@umituz/react-native-subscription 2.12.6 → 2.12.7
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.12.
|
|
3
|
+
"version": "2.12.7",
|
|
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,26 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Paywall Modal
|
|
3
|
-
*
|
|
3
|
+
* Modern paywall with gradient container and responsive design
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React, { useState, useCallback } from "react";
|
|
7
|
-
import { View, ScrollView, StyleSheet, ActivityIndicator } from "react-native";
|
|
8
|
-
import {
|
|
7
|
+
import { View, ScrollView, StyleSheet, TouchableOpacity, ActivityIndicator } from "react-native";
|
|
8
|
+
import { LinearGradient } from "expo-linear-gradient";
|
|
9
|
+
import {
|
|
10
|
+
BaseModal,
|
|
11
|
+
useAppDesignTokens,
|
|
12
|
+
AtomicText,
|
|
13
|
+
AtomicIcon,
|
|
14
|
+
useDesignSystemTheme,
|
|
15
|
+
} from "@umituz/react-native-design-system";
|
|
9
16
|
import type { PurchasesPackage } from "react-native-purchases";
|
|
10
|
-
import { PaywallHeader } from "./PaywallHeader";
|
|
11
|
-
import { PaywallTabBar } from "./PaywallTabBar";
|
|
12
|
-
import { PaywallFooter } from "./PaywallFooter";
|
|
13
|
-
import { FeatureList } from "./FeatureList";
|
|
14
17
|
import { PlanCard } from "./PlanCard";
|
|
15
18
|
import { CreditCard } from "./CreditCard";
|
|
16
|
-
import type {
|
|
17
|
-
PaywallMode,
|
|
18
|
-
PaywallTabType,
|
|
19
|
-
CreditsPackage,
|
|
20
|
-
SubscriptionFeature,
|
|
21
|
-
PaywallTranslations,
|
|
22
|
-
PaywallLegalUrls,
|
|
23
|
-
} from "../entities";
|
|
19
|
+
import type { PaywallMode, CreditsPackage, SubscriptionFeature, PaywallTranslations, PaywallLegalUrls } from "../entities";
|
|
24
20
|
|
|
25
21
|
export interface PaywallModalProps {
|
|
26
22
|
visible: boolean;
|
|
@@ -60,15 +56,19 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
|
|
|
60
56
|
} = props;
|
|
61
57
|
|
|
62
58
|
const tokens = useAppDesignTokens();
|
|
63
|
-
const
|
|
64
|
-
const
|
|
59
|
+
const { themeMode } = useDesignSystemTheme();
|
|
60
|
+
const isDark = themeMode === "dark";
|
|
61
|
+
|
|
65
62
|
const [selectedPlanId, setSelectedPlanId] = useState<string | null>(null);
|
|
66
63
|
const [selectedCreditId, setSelectedCreditId] = useState<string | null>(null);
|
|
67
64
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
68
65
|
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
66
|
+
const showCredits = mode === "credits";
|
|
67
|
+
const showSubscription = mode === "subscription" || mode === "hybrid";
|
|
68
|
+
|
|
69
|
+
const gradientColors: readonly [string, string, string] = isDark
|
|
70
|
+
? ["#1e3a5f", "#2d1e3f", "#1a1a2e"]
|
|
71
|
+
: ["#4a5568", "#5a4a78", "#3a3a4a"];
|
|
72
72
|
|
|
73
73
|
const handlePurchase = useCallback(async () => {
|
|
74
74
|
setIsProcessing(true);
|
|
@@ -85,44 +85,64 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
|
|
|
85
85
|
}, [showSubscription, showCredits, selectedPlanId, selectedCreditId, subscriptionPackages, onSubscriptionPurchase, onCreditsPurchase]);
|
|
86
86
|
|
|
87
87
|
const handleRestore = useCallback(async () => {
|
|
88
|
-
if (onRestore)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
88
|
+
if (!onRestore || isProcessing) return;
|
|
89
|
+
setIsProcessing(true);
|
|
90
|
+
try {
|
|
91
|
+
await onRestore();
|
|
92
|
+
} finally {
|
|
93
|
+
setIsProcessing(false);
|
|
95
94
|
}
|
|
96
|
-
}, [onRestore]);
|
|
95
|
+
}, [onRestore, isProcessing]);
|
|
97
96
|
|
|
98
97
|
const isPurchaseDisabled = showSubscription ? !selectedPlanId : !selectedCreditId;
|
|
99
98
|
|
|
100
99
|
return (
|
|
101
|
-
<BaseModal visible={visible} onClose={onClose}>
|
|
102
|
-
<
|
|
103
|
-
<
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
100
|
+
<BaseModal visible={visible} onClose={onClose} contentStyle={styles.modalContent}>
|
|
101
|
+
<LinearGradient colors={gradientColors} start={{ x: 0, y: 0 }} end={{ x: 1, y: 1 }} style={styles.gradient}>
|
|
102
|
+
<TouchableOpacity
|
|
103
|
+
onPress={onClose}
|
|
104
|
+
style={[styles.closeBtn, { backgroundColor: tokens.colors.surface }]}
|
|
105
|
+
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
106
|
+
>
|
|
107
|
+
<AtomicIcon name="close-outline" size="md" customColor={tokens.colors.textPrimary} />
|
|
108
|
+
</TouchableOpacity>
|
|
109
|
+
|
|
110
|
+
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={styles.scroll}>
|
|
111
|
+
<View style={styles.header}>
|
|
112
|
+
<AtomicText type="headlineLarge" style={[styles.title, { color: tokens.colors.onPrimary }]}>
|
|
113
|
+
{translations.title}
|
|
114
|
+
</AtomicText>
|
|
115
|
+
{translations.subtitle && (
|
|
116
|
+
<AtomicText type="bodyLarge" style={[styles.subtitle, { color: tokens.colors.onPrimary }]}>
|
|
117
|
+
{translations.subtitle}
|
|
118
|
+
</AtomicText>
|
|
119
|
+
)}
|
|
120
|
+
</View>
|
|
121
|
+
|
|
122
|
+
{features.length > 0 && (
|
|
123
|
+
<View style={[styles.features, { backgroundColor: `${tokens.colors.surface}15` }]}>
|
|
124
|
+
{features.map((feature, idx) => (
|
|
125
|
+
<View key={idx} style={styles.featureRow}>
|
|
126
|
+
<View style={[styles.featureIcon, { backgroundColor: tokens.colors.primaryLight }]}>
|
|
127
|
+
<AtomicIcon name={feature.icon} customSize={20} customColor={tokens.colors.primary} />
|
|
128
|
+
</View>
|
|
129
|
+
<AtomicText type="bodyLarge" style={[styles.featureText, { color: tokens.colors.onPrimary }]}>
|
|
130
|
+
{feature.text}
|
|
131
|
+
</AtomicText>
|
|
132
|
+
</View>
|
|
133
|
+
))}
|
|
134
|
+
</View>
|
|
135
|
+
)}
|
|
116
136
|
|
|
117
137
|
{isLoading ? (
|
|
118
|
-
<View style={styles.
|
|
119
|
-
<ActivityIndicator color={tokens.colors.
|
|
120
|
-
<AtomicText type="bodyMedium" style={{ color: tokens.colors.
|
|
138
|
+
<View style={styles.loading}>
|
|
139
|
+
<ActivityIndicator color={tokens.colors.onPrimary} />
|
|
140
|
+
<AtomicText type="bodyMedium" style={[styles.loadingText, { color: tokens.colors.onPrimary }]}>
|
|
121
141
|
{translations.loadingText}
|
|
122
142
|
</AtomicText>
|
|
123
143
|
</View>
|
|
124
144
|
) : (
|
|
125
|
-
|
|
145
|
+
<View style={styles.plans}>
|
|
126
146
|
{showSubscription &&
|
|
127
147
|
subscriptionPackages.map((pkg) => (
|
|
128
148
|
<PlanCard
|
|
@@ -135,34 +155,49 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
|
|
|
135
155
|
creditsLabel={creditsLabel}
|
|
136
156
|
/>
|
|
137
157
|
))}
|
|
138
|
-
|
|
139
158
|
{showCredits &&
|
|
140
159
|
creditsPackages.map((pkg) => (
|
|
141
|
-
<CreditCard
|
|
142
|
-
key={pkg.id}
|
|
143
|
-
pkg={pkg}
|
|
144
|
-
isSelected={selectedCreditId === pkg.id}
|
|
145
|
-
onSelect={() => setSelectedCreditId(pkg.id)}
|
|
146
|
-
/>
|
|
160
|
+
<CreditCard key={pkg.id} pkg={pkg} isSelected={selectedCreditId === pkg.id} onSelect={() => setSelectedCreditId(pkg.id)} />
|
|
147
161
|
))}
|
|
148
|
-
|
|
162
|
+
</View>
|
|
149
163
|
)}
|
|
150
|
-
</ScrollView>
|
|
151
164
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
<TouchableOpacity
|
|
166
|
+
onPress={handlePurchase}
|
|
167
|
+
disabled={isPurchaseDisabled || isProcessing}
|
|
168
|
+
style={[styles.cta, { backgroundColor: tokens.colors.primary }, (isPurchaseDisabled || isProcessing) && styles.ctaDisabled]}
|
|
169
|
+
activeOpacity={0.8}
|
|
170
|
+
>
|
|
171
|
+
<AtomicText type="titleLarge" style={[styles.ctaText, { color: tokens.colors.onPrimary }]}>
|
|
172
|
+
{isProcessing ? translations.processingText : showSubscription ? translations.subscribeButtonText : translations.purchaseButtonText}
|
|
173
|
+
</AtomicText>
|
|
174
|
+
</TouchableOpacity>
|
|
175
|
+
|
|
176
|
+
<View style={styles.footer}>
|
|
177
|
+
{legalUrls.termsUrl && (
|
|
178
|
+
<TouchableOpacity onPress={() => {}}>
|
|
179
|
+
<AtomicText type="bodySmall" style={[styles.footerLink, { color: tokens.colors.onPrimary }]}>
|
|
180
|
+
{translations.termsOfServiceText}
|
|
181
|
+
</AtomicText>
|
|
182
|
+
</TouchableOpacity>
|
|
183
|
+
)}
|
|
184
|
+
{onRestore && (
|
|
185
|
+
<TouchableOpacity onPress={handleRestore}>
|
|
186
|
+
<AtomicText type="bodySmall" style={[styles.footerLink, { color: tokens.colors.onPrimary }]}>
|
|
187
|
+
{translations.restoreButtonText}
|
|
188
|
+
</AtomicText>
|
|
189
|
+
</TouchableOpacity>
|
|
190
|
+
)}
|
|
191
|
+
{legalUrls.privacyUrl && (
|
|
192
|
+
<TouchableOpacity onPress={() => {}}>
|
|
193
|
+
<AtomicText type="bodySmall" style={[styles.footerLink, { color: tokens.colors.onPrimary }]}>
|
|
194
|
+
{translations.privacyText}
|
|
195
|
+
</AtomicText>
|
|
196
|
+
</TouchableOpacity>
|
|
197
|
+
)}
|
|
198
|
+
</View>
|
|
199
|
+
</ScrollView>
|
|
200
|
+
</LinearGradient>
|
|
166
201
|
</BaseModal>
|
|
167
202
|
);
|
|
168
203
|
});
|
|
@@ -170,18 +205,23 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
|
|
|
170
205
|
PaywallModal.displayName = "PaywallModal";
|
|
171
206
|
|
|
172
207
|
const styles = StyleSheet.create({
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
},
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
},
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
},
|
|
208
|
+
modalContent: { padding: 0, borderWidth: 0, overflow: "hidden" },
|
|
209
|
+
gradient: { flex: 1 },
|
|
210
|
+
closeBtn: { position: "absolute", top: 16, right: 16, width: 36, height: 36, borderRadius: 18, justifyContent: "center", alignItems: "center", zIndex: 10 },
|
|
211
|
+
scroll: { padding: 24, paddingTop: 56 },
|
|
212
|
+
header: { alignItems: "center", marginBottom: 24 },
|
|
213
|
+
title: { fontWeight: "700", textAlign: "center", marginBottom: 8 },
|
|
214
|
+
subtitle: { textAlign: "center", lineHeight: 24, opacity: 0.8 },
|
|
215
|
+
features: { borderRadius: 16, padding: 16, marginBottom: 20, gap: 12 },
|
|
216
|
+
featureRow: { flexDirection: "row", alignItems: "center" },
|
|
217
|
+
featureIcon: { width: 40, height: 40, borderRadius: 20, justifyContent: "center", alignItems: "center", marginRight: 12 },
|
|
218
|
+
featureText: { flex: 1, fontWeight: "500" },
|
|
219
|
+
loading: { alignItems: "center", paddingVertical: 40 },
|
|
220
|
+
loadingText: { marginTop: 12, opacity: 0.7 },
|
|
221
|
+
plans: { marginBottom: 20 },
|
|
222
|
+
cta: { borderRadius: 16, paddingVertical: 18, alignItems: "center", marginBottom: 16 },
|
|
223
|
+
ctaDisabled: { opacity: 0.5 },
|
|
224
|
+
ctaText: { fontWeight: "700" },
|
|
225
|
+
footer: { flexDirection: "row", justifyContent: "space-between", paddingHorizontal: 8 },
|
|
226
|
+
footerLink: { opacity: 0.6 },
|
|
187
227
|
});
|