@umituz/react-native-subscription 2.39.7 → 2.39.8

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.7",
3
+ "version": "2.39.8",
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,6 +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,
38
42
  paddingHorizontal: 16,
39
43
  paddingTop: 12,
40
44
  borderTopWidth: 1,
@@ -77,145 +77,157 @@ export const PaywallScreen: React.FC<PaywallScreenProps> = React.memo((props) =>
77
77
  }
78
78
  }, []);
79
79
 
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
- );
80
+ return (
81
+ <View style={[screenStyles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
82
+ {/* Close button - positioned absolutely at top */}
83
+ <View style={[screenStyles.headerContainer, { paddingTop: insets.top }]}>
84
+ <TouchableOpacity
85
+ onPress={onClose}
86
+ style={[screenStyles.closeBtn, { backgroundColor: tokens.colors.surfaceSecondary }]}
87
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
88
+ >
89
+ <AtomicIcon name="close-outline" size="md" customColor={tokens.colors.textPrimary} />
90
+ </TouchableOpacity>
91
+ </View>
90
92
 
91
- // Sticky footer with CTA and restore/legal links
92
- const footerContent = (
93
- <View style={[styles.stickyFooter, { paddingBottom: insets.bottom || 16 }]}>
94
- <TouchableOpacity
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}
93
+ {/* Scrollable content */}
94
+ <ScreenLayout
95
+ scrollable={true}
96
+ backgroundColor="transparent"
97
+ contentContainerStyle={{ ...screenStyles.contentContainer, paddingBottom: 120 }}
103
98
  >
104
- <AtomicText type="titleLarge" style={[styles.ctaText, { color: tokens.colors.onPrimary }]}>
105
- {isProcessing ? translations.processingText : translations.purchaseButtonText}
106
- </AtomicText>
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
- );
99
+ {/* Hero Image */}
100
+ {heroImage && (
101
+ <View style={styles.heroContainer}>
102
+ <Image source={heroImage} style={styles.heroImage} contentFit="cover" transition={0} />
103
+ </View>
104
+ )}
117
105
 
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 && (
106
+ {/* Header */}
107
+ <View style={styles.header}>
145
108
  <AtomicText
146
- type="bodyMedium"
109
+ type="headlineMedium"
147
110
  adjustsFontSizeToFit
148
- numberOfLines={3}
149
- minimumFontScale={0.8}
150
- style={[styles.subtitle, { color: tokens.colors.textSecondary }]}
111
+ numberOfLines={2}
112
+ minimumFontScale={0.75}
113
+ style={[styles.title, { color: tokens.colors.textPrimary }]}
151
114
  >
152
- {translations.subtitle}
115
+ {translations.title}
153
116
  </AtomicText>
154
- )}
155
- </View>
156
-
157
- {/* Features */}
158
- <PaywallFeatures features={features} />
159
-
160
- {/* Plans */}
161
- <View style={styles.plans}>
162
- {isLoadingPackages ? (
163
- <View style={styles.loading}>
164
- <AtomicSpinner size="md" />
165
- {translations.processingText && (
166
- <AtomicText
167
- type="bodySmall"
168
- style={[styles.loadingText, { color: tokens.colors.textSecondary }]}
169
- >
170
- {translations.processingText}
171
- </AtomicText>
172
- )}
173
- </View>
174
- ) : packages.length === 0 ? (
175
- <View style={styles.loading}>
117
+ {translations.subtitle && (
176
118
  <AtomicText
177
119
  type="bodyMedium"
178
- style={{ color: tokens.colors.textSecondary, textAlign: "center" }}
120
+ adjustsFontSizeToFit
121
+ numberOfLines={3}
122
+ minimumFontScale={0.8}
123
+ style={[styles.subtitle, { color: tokens.colors.textSecondary }]}
179
124
  >
180
- {translations.emptyText ?? "No packages available"}
125
+ {translations.subtitle}
181
126
  </AtomicText>
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
- )}
127
+ )}
128
+ </View>
129
+
130
+ {/* Features */}
131
+ <PaywallFeatures features={features} />
132
+
133
+ {/* Plans */}
134
+ <View style={styles.plans}>
135
+ {isLoadingPackages ? (
136
+ <View style={styles.loading}>
137
+ <AtomicSpinner size="md" />
138
+ {translations.processingText && (
139
+ <AtomicText
140
+ type="bodySmall"
141
+ style={[styles.loadingText, { color: tokens.colors.textSecondary }]}
142
+ >
143
+ {translations.processingText}
144
+ </AtomicText>
145
+ )}
146
+ </View>
147
+ ) : packages.length === 0 ? (
148
+ <View style={styles.loading}>
149
+ <AtomicText
150
+ type="bodyMedium"
151
+ style={{ color: tokens.colors.textSecondary, textAlign: "center" }}
152
+ >
153
+ {translations.emptyText ?? "No packages available"}
154
+ </AtomicText>
155
+ </View>
156
+ ) : (
157
+ packages.map((pkg) => {
158
+ const pid = pkg.product.identifier;
159
+ const isSelected = selectedPlanId === pid;
160
+ const isBestValue = bestValueIdentifier === pid;
161
+ const credits = creditAmounts?.[pid];
162
+
163
+ return (
164
+ <PlanCard
165
+ key={pid}
166
+ pkg={pkg}
167
+ isSelected={isSelected}
168
+ badge={isBestValue ? translations.bestValueBadgeText : undefined}
169
+ creditAmount={credits}
170
+ creditsLabel={creditsLabel}
171
+ onSelect={() => setSelectedPlanId(pid)}
172
+ />
173
+ );
174
+ })
175
+ )}
176
+ </View>
177
+ </ScreenLayout>
178
+
179
+ {/* Fixed footer overlay - always visible at bottom */}
180
+ <View style={[styles.stickyFooter, { backgroundColor: tokens.colors.backgroundPrimary, paddingBottom: Math.max(insets.bottom, 16) }]}>
181
+ <TouchableOpacity
182
+ onPress={handlePurchase}
183
+ disabled={isProcessing || isLoadingPackages || !selectedPlanId}
184
+ style={[
185
+ styles.cta,
186
+ { backgroundColor: tokens.colors.primary },
187
+ (isProcessing || isLoadingPackages || !selectedPlanId) && styles.ctaDisabled
188
+ ]}
189
+ activeOpacity={0.75}
190
+ >
191
+ <AtomicText type="titleLarge" style={[styles.ctaText, { color: tokens.colors.onPrimary }]}>
192
+ {isProcessing ? translations.processingText : translations.purchaseButtonText}
193
+ </AtomicText>
194
+ </TouchableOpacity>
195
+ <PaywallFooter
196
+ translations={translations}
197
+ legalUrls={legalUrls}
198
+ isProcessing={isProcessing}
199
+ onRestore={onRestore ? handleRestore : undefined}
200
+ onLegalClick={handleLegalUrl}
201
+ />
203
202
  </View>
204
- </ScreenLayout>
203
+ </View>
205
204
  );
206
205
  });
207
206
 
208
207
  PaywallScreen.displayName = "PaywallScreen";
209
208
 
210
209
  const screenStyles = StyleSheet.create({
210
+ container: {
211
+ flex: 1,
212
+ },
213
+ headerContainer: {
214
+ position: 'absolute',
215
+ top: 0,
216
+ left: 0,
217
+ right: 0,
218
+ zIndex: 1000,
219
+ paddingHorizontal: 12,
220
+ paddingTop: 12,
221
+ },
211
222
  closeBtn: {
212
223
  width: 36,
213
224
  height: 36,
214
225
  borderRadius: 18,
215
226
  justifyContent: 'center',
216
227
  alignItems: 'center',
228
+ alignSelf: 'flex-end',
217
229
  },
218
230
  contentContainer: {
219
- paddingBottom: 16,
231
+ paddingTop: 60, // Space for close button
220
232
  },
221
233
  });
@@ -139,7 +139,13 @@ 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 = (
143
+ tokens: {
144
+ colors: { backgroundPrimary: string; border: string };
145
+ spacing: { xl: number; sm: number; md: number };
146
+ },
147
+ _insets: { top: number; bottom: number }
148
+ ) => ({
143
149
  container: {
144
150
  flex: 1,
145
151
  },