@umituz/react-native-subscription 2.39.5 → 2.39.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.39.5",
3
+ "version": "2.39.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",
@@ -35,13 +35,10 @@ export const paywallScreenStyles = StyleSheet.create({
35
35
  marginTop: 12,
36
36
  },
37
37
  stickyFooter: {
38
- position: "absolute",
39
- bottom: 0,
40
- left: 0,
41
- right: 0,
42
38
  paddingHorizontal: 16,
43
39
  paddingTop: 12,
44
- backgroundColor: "#000",
40
+ borderTopWidth: 1,
41
+ borderTopColor: "rgba(255, 255, 255, 0.1)",
45
42
  },
46
43
  cta: {
47
44
  borderRadius: 14,
@@ -6,10 +6,11 @@
6
6
  */
7
7
 
8
8
  import React, { useCallback, useEffect } from "react";
9
- import { View, ScrollView, TouchableOpacity, Linking, StyleSheet } from "react-native";
9
+ import { View, TouchableOpacity, Linking, StyleSheet } from "react-native";
10
10
  import { AtomicText, AtomicIcon, AtomicSpinner } from "@umituz/react-native-design-system/atoms";
11
11
  import { useSafeAreaInsets } from "@umituz/react-native-design-system/safe-area";
12
12
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
13
+ import { ScreenLayout } from "@umituz/react-native-design-system/layouts";
13
14
  import { Image } from "expo-image";
14
15
  import { PlanCard } from "./PlanCard";
15
16
  import { paywallScreenStyles as styles } from "./PaywallScreen.styles";
@@ -76,148 +77,145 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
76
77
  }
77
78
  }, []);
78
79
 
79
- return (
80
- <View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
81
- {/* Close button */}
80
+ // Close button for header
81
+ const closeButton = (
82
+ <TouchableOpacity
83
+ onPress={onClose}
84
+ style={[screenStyles.closeBtn, { backgroundColor: tokens.colors.surfaceSecondary }]}
85
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
86
+ >
87
+ <AtomicIcon name="close-outline" size="md" customColor={tokens.colors.textPrimary} />
88
+ </TouchableOpacity>
89
+ );
90
+
91
+ // Sticky footer with CTA and restore/legal links
92
+ const footerContent = (
93
+ <View style={[styles.stickyFooter, { paddingBottom: insets.bottom || 16 }]}>
82
94
  <TouchableOpacity
83
- onPress={onClose}
84
- style={[screenStyles.closeBtn, { backgroundColor: tokens.colors.surfaceSecondary, top: Math.max(insets.top, 12) }]}
85
- hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
95
+ onPress={handlePurchase}
96
+ disabled={isProcessing || isLoadingPackages || !selectedPlanId}
97
+ style={[
98
+ styles.cta,
99
+ { backgroundColor: tokens.colors.primary },
100
+ (isProcessing || isLoadingPackages || !selectedPlanId) && styles.ctaDisabled
101
+ ]}
102
+ activeOpacity={0.75}
86
103
  >
87
- <AtomicIcon name="close-outline" size="md" customColor={tokens.colors.textPrimary} />
104
+ <AtomicText type="titleLarge" style={[styles.ctaText, { color: tokens.colors.onPrimary }]}>
105
+ {isProcessing ? translations.processingText : translations.purchaseButtonText}
106
+ </AtomicText>
88
107
  </TouchableOpacity>
108
+ <PaywallFooter
109
+ translations={translations}
110
+ legalUrls={legalUrls}
111
+ isProcessing={isProcessing}
112
+ onRestore={onRestore ? handleRestore : undefined}
113
+ onLegalClick={handleLegalUrl}
114
+ />
115
+ </View>
116
+ );
89
117
 
90
- {/* Scrollable content */}
91
- <ScrollView
92
- style={screenStyles.scrollContainer}
93
- contentContainerStyle={screenStyles.scroll}
94
- showsVerticalScrollIndicator={false}
95
- >
96
- {/* Hero Image */}
97
- {heroImage && (
98
- <View style={styles.heroContainer}>
99
- <Image source={heroImage} style={styles.heroImage} contentFit="cover" transition={0} />
100
- </View>
101
- )}
102
-
103
- {/* Header */}
104
- <View style={styles.header}>
118
+ return (
119
+ <ScreenLayout
120
+ scrollable={true}
121
+ backgroundColor={tokens.colors.backgroundPrimary}
122
+ header={closeButton}
123
+ footer={footerContent}
124
+ contentContainerStyle={screenStyles.contentContainer}
125
+ >
126
+ {/* Hero Image */}
127
+ {heroImage && (
128
+ <View style={styles.heroContainer}>
129
+ <Image source={heroImage} style={styles.heroImage} contentFit="cover" transition={0} />
130
+ </View>
131
+ )}
132
+
133
+ {/* Header */}
134
+ <View style={styles.header}>
135
+ <AtomicText
136
+ type="headlineMedium"
137
+ adjustsFontSizeToFit
138
+ numberOfLines={2}
139
+ minimumFontScale={0.75}
140
+ style={[styles.title, { color: tokens.colors.textPrimary }]}
141
+ >
142
+ {translations.title}
143
+ </AtomicText>
144
+ {translations.subtitle && (
105
145
  <AtomicText
106
- type="headlineMedium"
146
+ type="bodyMedium"
107
147
  adjustsFontSizeToFit
108
- numberOfLines={2}
109
- minimumFontScale={0.75}
110
- style={[styles.title, { color: tokens.colors.textPrimary }]}
148
+ numberOfLines={3}
149
+ minimumFontScale={0.8}
150
+ style={[styles.subtitle, { color: tokens.colors.textSecondary }]}
111
151
  >
112
- {translations.title}
152
+ {translations.subtitle}
113
153
  </AtomicText>
114
- {translations.subtitle && (
115
- <AtomicText
116
- type="bodyMedium"
117
- adjustsFontSizeToFit
118
- numberOfLines={3}
119
- minimumFontScale={0.8}
120
- style={[styles.subtitle, { color: tokens.colors.textSecondary }]}
121
- >
122
- {translations.subtitle}
123
- </AtomicText>
124
- )}
125
- </View>
154
+ )}
155
+ </View>
156
+
157
+ {/* Features */}
158
+ <PaywallFeatures features={features} />
126
159
 
127
- {/* Features */}
128
- <PaywallFeatures features={features} />
129
-
130
- {/* Plans */}
131
- <View style={styles.plans}>
132
- {isLoadingPackages ? (
133
- <View style={styles.loading}>
134
- <AtomicSpinner size="md" />
135
- {translations.processingText && (
136
- <AtomicText
137
- type="bodySmall"
138
- style={[styles.loadingText, { color: tokens.colors.textSecondary }]}
139
- >
140
- {translations.processingText}
141
- </AtomicText>
142
- )}
143
- </View>
144
- ) : packages.length === 0 ? (
145
- <View style={styles.loading}>
160
+ {/* Plans */}
161
+ <View style={styles.plans}>
162
+ {isLoadingPackages ? (
163
+ <View style={styles.loading}>
164
+ <AtomicSpinner size="md" />
165
+ {translations.processingText && (
146
166
  <AtomicText
147
- type="bodyMedium"
148
- style={{ color: tokens.colors.textSecondary, textAlign: "center" }}
167
+ type="bodySmall"
168
+ style={[styles.loadingText, { color: tokens.colors.textSecondary }]}
149
169
  >
150
- {translations.emptyText ?? "No packages available"}
170
+ {translations.processingText}
151
171
  </AtomicText>
152
- </View>
153
- ) : (
154
- packages.map((pkg) => {
155
- const pid = pkg.product.identifier;
156
- const isSelected = selectedPlanId === pid;
157
- const isBestValue = bestValueIdentifier === pid;
158
- const credits = creditAmounts?.[pid];
159
-
160
- return (
161
- <PlanCard
162
- key={pid}
163
- pkg={pkg}
164
- isSelected={isSelected}
165
- badge={isBestValue ? translations.bestValueBadgeText : undefined}
166
- creditAmount={credits}
167
- creditsLabel={creditsLabel}
168
- onSelect={() => setSelectedPlanId(pid)}
169
- />
170
- );
171
- })
172
- )}
173
- </View>
174
-
175
- {/* Sticky footer — always visible, never hidden behind scroll content */}
176
- <View style={[styles.stickyFooter, { paddingBottom: Math.max(insets.bottom, 16) }]}>
177
- <TouchableOpacity
178
- onPress={handlePurchase}
179
- disabled={isProcessing || isLoadingPackages || !selectedPlanId}
180
- style={[styles.cta, { backgroundColor: tokens.colors.primary }, (isProcessing || isLoadingPackages || !selectedPlanId) && styles.ctaDisabled]}
181
- activeOpacity={0.75}
182
- >
183
- <AtomicText type="titleLarge" style={[styles.ctaText, { color: tokens.colors.onPrimary }]}>
184
- {isProcessing ? translations.processingText : translations.purchaseButtonText}
172
+ )}
173
+ </View>
174
+ ) : packages.length === 0 ? (
175
+ <View style={styles.loading}>
176
+ <AtomicText
177
+ type="bodyMedium"
178
+ style={{ color: tokens.colors.textSecondary, textAlign: "center" }}
179
+ >
180
+ {translations.emptyText ?? "No packages available"}
185
181
  </AtomicText>
186
- </TouchableOpacity>
187
- <PaywallFooter
188
- translations={translations}
189
- legalUrls={legalUrls}
190
- isProcessing={isProcessing}
191
- onRestore={onRestore ? handleRestore : undefined}
192
- onLegalClick={handleLegalUrl}
193
- />
194
- </View>
195
- </ScrollView>
196
- </View>
182
+ </View>
183
+ ) : (
184
+ packages.map((pkg) => {
185
+ const pid = pkg.product.identifier;
186
+ const isSelected = selectedPlanId === pid;
187
+ const isBestValue = bestValueIdentifier === pid;
188
+ const credits = creditAmounts?.[pid];
189
+
190
+ return (
191
+ <PlanCard
192
+ key={pid}
193
+ pkg={pkg}
194
+ isSelected={isSelected}
195
+ badge={isBestValue ? translations.bestValueBadgeText : undefined}
196
+ creditAmount={credits}
197
+ creditsLabel={creditsLabel}
198
+ onSelect={() => setSelectedPlanId(pid)}
199
+ />
200
+ );
201
+ })
202
+ )}
203
+ </View>
204
+ </ScreenLayout>
197
205
  );
198
206
  });
199
207
 
200
208
  PaywallScreen.displayName = "PaywallScreen";
201
209
 
202
210
  const screenStyles = StyleSheet.create({
203
- container: {
204
- flex: 1,
205
- },
206
211
  closeBtn: {
207
- position: 'absolute',
208
- top: 12,
209
- right: 12,
210
212
  width: 36,
211
213
  height: 36,
212
214
  borderRadius: 18,
213
- zIndex: 1000,
214
215
  justifyContent: 'center',
215
216
  alignItems: 'center',
216
217
  },
217
- scrollContainer: {
218
- flex: 1,
219
- },
220
- scroll: {
221
- paddingBottom: 100,
218
+ contentContainer: {
219
+ paddingBottom: 16,
222
220
  },
223
221
  });
@@ -21,7 +21,7 @@ export const FeedbackOption: React.FC<FeedbackOptionProps> = React.memo(({
21
21
 
22
22
  const containerStyle = {
23
23
  marginBottom: tokens.spacing.sm,
24
- backgroundColor: tokens.colors.surfaceVariant,
24
+ backgroundColor: tokens.colors.surfaceSecondary,
25
25
  borderRadius: tokens.borderRadius.md,
26
26
  overflow: "hidden" as const,
27
27
  };
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  import React, { useMemo, useCallback } from "react";
10
- import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
10
+ import { View, ScrollView, TouchableOpacity } from "react-native";
11
11
  import { AtomicText, AtomicIcon } from "@umituz/react-native-design-system/atoms";
12
12
  import { useSafeAreaInsets } from "@umituz/react-native-design-system/safe-area";
13
13
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
@@ -114,7 +114,7 @@ export const PaywallFeedbackScreen: React.FC<PaywallFeedbackScreenProps> = React
114
114
  style={[
115
115
  screenStyles.submitButton,
116
116
  {
117
- backgroundColor: canSubmit ? tokens.colors.primary : tokens.colors.surfaceVariant,
117
+ backgroundColor: canSubmit ? tokens.colors.primary : tokens.colors.surfaceSecondary,
118
118
  opacity: canSubmit ? 1 : 0.6,
119
119
  }
120
120
  ]}
@@ -139,7 +139,7 @@ export const PaywallFeedbackScreen: React.FC<PaywallFeedbackScreenProps> = React
139
139
 
140
140
  PaywallFeedbackScreen.displayName = "PaywallFeedbackScreen";
141
141
 
142
- const createScreenStyles = (tokens: any, insets: any) => ({
142
+ const createScreenStyles = (tokens: any, _insets: any) => ({
143
143
  container: {
144
144
  flex: 1,
145
145
  },
@@ -39,7 +39,7 @@ export const SubscriptionDetailScreen: React.FC<SubscriptionDetailScreenProps> =
39
39
  onPress={config.onClose}
40
40
  style={({ pressed }) => ({
41
41
  width: 44, height: 44, justifyContent: "center", alignItems: "center",
42
- backgroundColor: pressed ? tokens.colors.surfaceVariant : tokens.colors.surface,
42
+ backgroundColor: pressed ? tokens.colors.surfaceSecondary : tokens.colors.surface,
43
43
  borderRadius: tokens.radius.full,
44
44
  })}
45
45
  >