@umituz/react-native-subscription 2.2.20 → 2.2.22
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 +1 -1
- package/src/index.ts +3 -0
- package/src/presentation/components/paywall/PaywallFeatureItem.tsx +1 -1
- package/src/presentation/components/paywall/PaywallFeaturesList.tsx +3 -0
- package/src/presentation/components/paywall/PaywallLegalFooter.tsx +6 -3
- package/src/presentation/components/paywall/SubscriptionModal.tsx +95 -206
- package/src/presentation/components/paywall/SubscriptionModalHeader.tsx +82 -0
- package/src/presentation/components/paywall/SubscriptionModalOverlay.tsx +122 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.22",
|
|
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
|
@@ -96,6 +96,9 @@ export {
|
|
|
96
96
|
type SubscriptionModalProps,
|
|
97
97
|
} from "./presentation/components/paywall/SubscriptionModal";
|
|
98
98
|
|
|
99
|
+
export { SubscriptionModalHeader } from "./presentation/components/paywall/SubscriptionModalHeader";
|
|
100
|
+
export { SubscriptionModalOverlay, type SubscriptionModalVariant } from "./presentation/components/paywall/SubscriptionModalOverlay";
|
|
101
|
+
|
|
99
102
|
export { SubscriptionPlanCard } from "./presentation/components/paywall/SubscriptionPlanCard";
|
|
100
103
|
export { PaywallFeaturesList } from "./presentation/components/paywall/PaywallFeaturesList";
|
|
101
104
|
export { PaywallFeatureItem } from "./presentation/components/paywall/PaywallFeatureItem";
|
|
@@ -34,7 +34,7 @@ export const PaywallFeatureItem: React.FC<PaywallFeatureItemProps> = React.memo(
|
|
|
34
34
|
/>
|
|
35
35
|
<AtomicText
|
|
36
36
|
type="bodyMedium"
|
|
37
|
-
style={[styles.featureText, { color: tokens.colors.
|
|
37
|
+
style={[styles.featureText, { color: tokens.colors.textPrimary }]}
|
|
38
38
|
>
|
|
39
39
|
{text}
|
|
40
40
|
</AtomicText>
|
|
@@ -18,6 +18,9 @@ interface PaywallFeaturesListProps {
|
|
|
18
18
|
|
|
19
19
|
export const PaywallFeaturesList: React.FC<PaywallFeaturesListProps> = React.memo(
|
|
20
20
|
({ features, containerStyle, gap = 12 }) => {
|
|
21
|
+
if (__DEV__) {
|
|
22
|
+
console.log("[PaywallFeaturesList] Rendering features count:", features.length);
|
|
23
|
+
}
|
|
21
24
|
return (
|
|
22
25
|
<View style={[styles.container, containerStyle]}>
|
|
23
26
|
{features.map((feature, index) => (
|
|
@@ -92,25 +92,28 @@ const styles = StyleSheet.create({
|
|
|
92
92
|
container: {
|
|
93
93
|
alignItems: "center",
|
|
94
94
|
paddingHorizontal: 24,
|
|
95
|
-
paddingBottom:
|
|
95
|
+
paddingBottom: 32, // Increased for home indicator safety
|
|
96
96
|
paddingTop: 8,
|
|
97
|
+
width: "100%",
|
|
97
98
|
},
|
|
98
99
|
termsText: {
|
|
99
100
|
textAlign: "center",
|
|
100
101
|
fontSize: 11,
|
|
101
102
|
lineHeight: 16,
|
|
102
|
-
marginBottom:
|
|
103
|
+
marginBottom: 10,
|
|
103
104
|
},
|
|
104
105
|
legalLinksContainer: {
|
|
105
106
|
flexDirection: "row",
|
|
106
107
|
alignItems: "center",
|
|
108
|
+
justifyContent: "center",
|
|
107
109
|
marginTop: 4,
|
|
110
|
+
flexWrap: "wrap",
|
|
108
111
|
},
|
|
109
112
|
linkText: {
|
|
110
113
|
textDecorationLine: "underline",
|
|
111
114
|
fontSize: 12,
|
|
112
115
|
},
|
|
113
116
|
separator: {
|
|
114
|
-
marginHorizontal:
|
|
117
|
+
marginHorizontal: 8,
|
|
115
118
|
},
|
|
116
119
|
});
|
|
@@ -1,27 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Subscription Modal Component
|
|
3
|
-
* Orchestrates subscription flow
|
|
3
|
+
* Orchestrates subscription flow using decomposed components
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React, { useState, useCallback } from "react";
|
|
7
|
-
import {
|
|
8
|
-
View,
|
|
9
|
-
Modal,
|
|
10
|
-
StyleSheet,
|
|
11
|
-
TouchableOpacity,
|
|
12
|
-
ScrollView,
|
|
13
|
-
Dimensions,
|
|
14
|
-
} from "react-native";
|
|
7
|
+
import { View, StyleSheet, ScrollView } from "react-native";
|
|
15
8
|
import { SafeAreaView } from "react-native-safe-area-context";
|
|
16
|
-
import type { PurchasesPackage } from "react-native-purchases";
|
|
17
|
-
import { AtomicText } from "@umituz/react-native-design-system-atoms";
|
|
18
9
|
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
10
|
+
import type { PurchasesPackage } from "react-native-purchases";
|
|
11
|
+
|
|
12
|
+
import { SubscriptionModalHeader } from "./SubscriptionModalHeader";
|
|
13
|
+
import { SubscriptionModalOverlay, SubscriptionModalVariant } from "./SubscriptionModalOverlay";
|
|
19
14
|
import { PaywallFeaturesList } from "./PaywallFeaturesList";
|
|
20
15
|
import { SubscriptionPackageList } from "./SubscriptionPackageList";
|
|
21
16
|
import { SubscriptionFooter } from "./SubscriptionFooter";
|
|
22
17
|
|
|
23
|
-
const { height: SCREEN_HEIGHT } = Dimensions.get("window");
|
|
24
|
-
|
|
25
18
|
export interface SubscriptionModalProps {
|
|
26
19
|
visible: boolean;
|
|
27
20
|
onClose: () => void;
|
|
@@ -42,12 +35,12 @@ export interface SubscriptionModalProps {
|
|
|
42
35
|
privacyText?: string;
|
|
43
36
|
termsOfServiceText?: string;
|
|
44
37
|
showRestoreButton?: boolean;
|
|
45
|
-
variant?:
|
|
38
|
+
variant?: SubscriptionModalVariant;
|
|
46
39
|
BackgroundComponent?: React.ComponentType<any>;
|
|
47
40
|
}
|
|
48
41
|
|
|
49
|
-
export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo(
|
|
50
|
-
|
|
42
|
+
export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo((props) => {
|
|
43
|
+
const {
|
|
51
44
|
visible,
|
|
52
45
|
onClose,
|
|
53
46
|
packages,
|
|
@@ -59,8 +52,8 @@ export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo(
|
|
|
59
52
|
isLoading = false,
|
|
60
53
|
purchaseButtonText = "Subscribe",
|
|
61
54
|
restoreButtonText = "Restore Purchases",
|
|
62
|
-
loadingText = "Loading
|
|
63
|
-
emptyText = "No packages
|
|
55
|
+
loadingText = "Loading...",
|
|
56
|
+
emptyText = "No packages",
|
|
64
57
|
processingText = "Processing...",
|
|
65
58
|
privacyUrl,
|
|
66
59
|
termsUrl,
|
|
@@ -68,215 +61,111 @@ export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo(
|
|
|
68
61
|
termsOfServiceText,
|
|
69
62
|
showRestoreButton = true,
|
|
70
63
|
variant = "bottom-sheet",
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
}, [selectedPkg, isProcessing, onPurchase, onClose]);
|
|
64
|
+
} = props;
|
|
65
|
+
|
|
66
|
+
const tokens = useAppDesignTokens();
|
|
67
|
+
const [selectedPkg, setSelectedPkg] = useState<PurchasesPackage | null>(null);
|
|
68
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
69
|
+
|
|
70
|
+
if (__DEV__ && visible) {
|
|
71
|
+
console.log("[SubscriptionModal] Props Info:", {
|
|
72
|
+
packagesCount: packages.length,
|
|
73
|
+
featuresCount: features.length,
|
|
74
|
+
variant,
|
|
75
|
+
hasPrivacyUrl: !!privacyUrl,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
87
78
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
79
|
+
const handlePurchase = useCallback(async () => {
|
|
80
|
+
if (!selectedPkg || isProcessing) return;
|
|
81
|
+
setIsProcessing(true);
|
|
82
|
+
try {
|
|
83
|
+
if (await onPurchase(selectedPkg)) onClose();
|
|
84
|
+
} finally {
|
|
85
|
+
setIsProcessing(false);
|
|
86
|
+
}
|
|
87
|
+
}, [selectedPkg, isProcessing, onPurchase, onClose]);
|
|
88
|
+
|
|
89
|
+
const handleRestore = useCallback(async () => {
|
|
90
|
+
if (isProcessing) return;
|
|
91
|
+
setIsProcessing(true);
|
|
92
|
+
try {
|
|
93
|
+
if (await onRestore()) onClose();
|
|
94
|
+
} finally {
|
|
95
|
+
setIsProcessing(false);
|
|
96
|
+
}
|
|
97
|
+
}, [isProcessing, onRestore, onClose]);
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
if (!visible) return null;
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
const ContentWrapper = variant === "fullscreen" ? SafeAreaView : View;
|
|
102
|
+
const wrapperProps = variant === "fullscreen" ? { edges: ["top", "bottom"] as const } : {};
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
return (
|
|
105
|
+
<SubscriptionModalOverlay visible={visible} onClose={onClose} variant={variant}>
|
|
106
|
+
<ContentWrapper {...wrapperProps} style={styles.container}>
|
|
107
|
+
<SubscriptionModalHeader title={title} subtitle={subtitle} onClose={onClose} />
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
109
|
+
<ScrollView
|
|
110
|
+
style={styles.scrollView}
|
|
111
|
+
contentContainerStyle={styles.scrollContent}
|
|
112
|
+
showsVerticalScrollIndicator={false}
|
|
113
|
+
bounces={false}
|
|
114
|
+
>
|
|
115
|
+
<SubscriptionPackageList
|
|
116
|
+
packages={packages}
|
|
117
|
+
isLoading={isLoading}
|
|
118
|
+
selectedPkg={selectedPkg}
|
|
119
|
+
onSelect={setSelectedPkg}
|
|
120
|
+
loadingText={loadingText}
|
|
121
|
+
emptyText={emptyText}
|
|
122
|
+
/>
|
|
123
|
+
|
|
124
|
+
{features.length > 0 && (
|
|
125
|
+
<View style={[styles.featuresSection, { backgroundColor: tokens.colors.surfaceSecondary }]}>
|
|
126
|
+
<PaywallFeaturesList features={features} gap={12} />
|
|
127
|
+
</View>
|
|
128
|
+
)}
|
|
129
|
+
</ScrollView>
|
|
130
|
+
|
|
131
|
+
<SubscriptionFooter
|
|
132
|
+
isProcessing={isProcessing}
|
|
117
133
|
isLoading={isLoading}
|
|
134
|
+
processingText={processingText}
|
|
135
|
+
purchaseButtonText={purchaseButtonText}
|
|
136
|
+
hasPackages={packages.length > 0}
|
|
118
137
|
selectedPkg={selectedPkg}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
138
|
+
restoreButtonText={restoreButtonText}
|
|
139
|
+
showRestoreButton={showRestoreButton}
|
|
140
|
+
privacyUrl={privacyUrl}
|
|
141
|
+
termsUrl={termsUrl}
|
|
142
|
+
privacyText={privacyText}
|
|
143
|
+
termsOfServiceText={termsOfServiceText}
|
|
144
|
+
onPurchase={handlePurchase}
|
|
145
|
+
onRestore={handleRestore}
|
|
122
146
|
/>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
)}
|
|
128
|
-
</ScrollView>
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
// Footer with buttons and legal links
|
|
132
|
-
const Footer = (
|
|
133
|
-
<SubscriptionFooter
|
|
134
|
-
isProcessing={isProcessing}
|
|
135
|
-
isLoading={isLoading}
|
|
136
|
-
processingText={processingText}
|
|
137
|
-
purchaseButtonText={purchaseButtonText}
|
|
138
|
-
hasPackages={packages.length > 0}
|
|
139
|
-
selectedPkg={selectedPkg}
|
|
140
|
-
restoreButtonText={restoreButtonText}
|
|
141
|
-
showRestoreButton={showRestoreButton}
|
|
142
|
-
privacyUrl={privacyUrl}
|
|
143
|
-
termsUrl={termsUrl}
|
|
144
|
-
privacyText={privacyText}
|
|
145
|
-
termsOfServiceText={termsOfServiceText}
|
|
146
|
-
onPurchase={handlePurchase}
|
|
147
|
-
onRestore={handleRestore}
|
|
148
|
-
/>
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
// Header with title and close button
|
|
152
|
-
const Header = (
|
|
153
|
-
<View style={styles.header}>
|
|
154
|
-
<TouchableOpacity style={styles.closeButton} onPress={onClose}>
|
|
155
|
-
<AtomicText style={[styles.closeIcon, { color: tokens.colors.textSecondary }]}>
|
|
156
|
-
×
|
|
157
|
-
</AtomicText>
|
|
158
|
-
</TouchableOpacity>
|
|
159
|
-
<AtomicText
|
|
160
|
-
type="headlineMedium"
|
|
161
|
-
style={[styles.title, { color: tokens.colors.textPrimary }]}
|
|
162
|
-
>
|
|
163
|
-
{title}
|
|
164
|
-
</AtomicText>
|
|
165
|
-
{subtitle && (
|
|
166
|
-
<AtomicText
|
|
167
|
-
type="bodyMedium"
|
|
168
|
-
style={[styles.subtitle, { color: tokens.colors.textSecondary }]}
|
|
169
|
-
>
|
|
170
|
-
{subtitle}
|
|
171
|
-
</AtomicText>
|
|
172
|
-
)}
|
|
173
|
-
</View>
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
// FULLSCREEN VARIANT
|
|
177
|
-
if (isFullScreen) {
|
|
178
|
-
const Wrapper = BackgroundComponent || View;
|
|
179
|
-
const wrapperStyle = !BackgroundComponent
|
|
180
|
-
? { flex: 1, backgroundColor: tokens.colors.backgroundPrimary }
|
|
181
|
-
: { flex: 1 };
|
|
182
|
-
|
|
183
|
-
return (
|
|
184
|
-
<Modal visible={visible} transparent={false} animationType="slide" onRequestClose={onClose}>
|
|
185
|
-
<Wrapper style={wrapperStyle}>
|
|
186
|
-
<ContentWrapper {...wrapperProps} style={styles.fullscreenContainer}>
|
|
187
|
-
{Header}
|
|
188
|
-
{ScrollContent}
|
|
189
|
-
{Footer}
|
|
190
|
-
</ContentWrapper>
|
|
191
|
-
</Wrapper>
|
|
192
|
-
</Modal>
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// BOTTOM-SHEET VARIANT
|
|
197
|
-
return (
|
|
198
|
-
<Modal visible={visible} transparent animationType="slide" onRequestClose={onClose}>
|
|
199
|
-
<View style={styles.overlay}>
|
|
200
|
-
<TouchableOpacity style={styles.backdrop} activeOpacity={1} onPress={onClose} />
|
|
201
|
-
<View style={[styles.bottomSheetContainer, { backgroundColor: tokens.colors.surface }]}>
|
|
202
|
-
{Header}
|
|
203
|
-
{ScrollContent}
|
|
204
|
-
{Footer}
|
|
205
|
-
</View>
|
|
206
|
-
</View>
|
|
207
|
-
</Modal>
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
);
|
|
147
|
+
</ContentWrapper>
|
|
148
|
+
</SubscriptionModalOverlay>
|
|
149
|
+
);
|
|
150
|
+
});
|
|
211
151
|
|
|
212
152
|
SubscriptionModal.displayName = "SubscriptionModal";
|
|
213
153
|
|
|
214
154
|
const styles = StyleSheet.create({
|
|
215
|
-
|
|
216
|
-
overlay: {
|
|
155
|
+
container: {
|
|
217
156
|
flex: 1,
|
|
218
|
-
justifyContent: "flex-end",
|
|
219
157
|
},
|
|
220
|
-
backdrop: {
|
|
221
|
-
...StyleSheet.absoluteFillObject,
|
|
222
|
-
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
223
|
-
},
|
|
224
|
-
|
|
225
|
-
// Bottom-sheet container - KEY FIX: explicit max height calculation
|
|
226
|
-
bottomSheetContainer: {
|
|
227
|
-
borderTopLeftRadius: 24,
|
|
228
|
-
borderTopRightRadius: 24,
|
|
229
|
-
maxHeight: SCREEN_HEIGHT * 0.85,
|
|
230
|
-
paddingTop: 16,
|
|
231
|
-
paddingBottom: 24,
|
|
232
|
-
},
|
|
233
|
-
|
|
234
|
-
// Fullscreen container
|
|
235
|
-
fullscreenContainer: {
|
|
236
|
-
flex: 1,
|
|
237
|
-
paddingTop: 16,
|
|
238
|
-
},
|
|
239
|
-
|
|
240
|
-
// Header
|
|
241
|
-
header: {
|
|
242
|
-
alignItems: "center",
|
|
243
|
-
paddingHorizontal: 24,
|
|
244
|
-
paddingBottom: 16,
|
|
245
|
-
},
|
|
246
|
-
closeButton: {
|
|
247
|
-
position: "absolute",
|
|
248
|
-
top: 0,
|
|
249
|
-
right: 16,
|
|
250
|
-
padding: 8,
|
|
251
|
-
zIndex: 1,
|
|
252
|
-
},
|
|
253
|
-
closeIcon: {
|
|
254
|
-
fontSize: 28,
|
|
255
|
-
fontWeight: "300",
|
|
256
|
-
},
|
|
257
|
-
title: {
|
|
258
|
-
marginBottom: 8,
|
|
259
|
-
textAlign: "center",
|
|
260
|
-
},
|
|
261
|
-
subtitle: {
|
|
262
|
-
textAlign: "center",
|
|
263
|
-
},
|
|
264
|
-
|
|
265
|
-
// ScrollView - expands to fill available space, shrinks if needed
|
|
266
158
|
scrollView: {
|
|
267
159
|
flex: 1,
|
|
268
|
-
minHeight: 100,
|
|
269
160
|
},
|
|
270
161
|
scrollContent: {
|
|
271
162
|
paddingHorizontal: 24,
|
|
272
|
-
paddingBottom:
|
|
163
|
+
paddingBottom: 24,
|
|
273
164
|
flexGrow: 1,
|
|
274
165
|
},
|
|
275
|
-
|
|
276
|
-
// Features section
|
|
277
166
|
featuresSection: {
|
|
278
|
-
borderRadius:
|
|
279
|
-
padding:
|
|
280
|
-
marginTop:
|
|
167
|
+
borderRadius: 20,
|
|
168
|
+
padding: 20,
|
|
169
|
+
marginTop: 8,
|
|
281
170
|
},
|
|
282
171
|
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscription Modal Header Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import React from "react";
|
|
6
|
+
import { View, StyleSheet, TouchableOpacity } from "react-native";
|
|
7
|
+
import { AtomicText } from "@umituz/react-native-design-system-atoms";
|
|
8
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
9
|
+
|
|
10
|
+
interface SubscriptionModalHeaderProps {
|
|
11
|
+
title: string;
|
|
12
|
+
subtitle?: string;
|
|
13
|
+
onClose: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const SubscriptionModalHeader: React.FC<SubscriptionModalHeaderProps> = ({
|
|
17
|
+
title,
|
|
18
|
+
subtitle,
|
|
19
|
+
onClose,
|
|
20
|
+
}) => {
|
|
21
|
+
const tokens = useAppDesignTokens();
|
|
22
|
+
|
|
23
|
+
if (__DEV__) {
|
|
24
|
+
console.log("[SubscriptionModalHeader] Rendering title:", title);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<View style={styles.header}>
|
|
29
|
+
<TouchableOpacity
|
|
30
|
+
style={styles.closeButton}
|
|
31
|
+
onPress={onClose}
|
|
32
|
+
testID="subscription-modal-close-button"
|
|
33
|
+
>
|
|
34
|
+
<AtomicText style={[styles.closeIcon, { color: tokens.colors.textSecondary }]}>
|
|
35
|
+
×
|
|
36
|
+
</AtomicText>
|
|
37
|
+
</TouchableOpacity>
|
|
38
|
+
<AtomicText
|
|
39
|
+
type="headlineMedium"
|
|
40
|
+
style={[styles.title, { color: tokens.colors.textPrimary }]}
|
|
41
|
+
>
|
|
42
|
+
{title}
|
|
43
|
+
</AtomicText>
|
|
44
|
+
{subtitle && (
|
|
45
|
+
<AtomicText
|
|
46
|
+
type="bodyMedium"
|
|
47
|
+
style={[styles.subtitle, { color: tokens.colors.textSecondary }]}
|
|
48
|
+
>
|
|
49
|
+
{subtitle}
|
|
50
|
+
</AtomicText>
|
|
51
|
+
)}
|
|
52
|
+
</View>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const styles = StyleSheet.create({
|
|
57
|
+
header: {
|
|
58
|
+
alignItems: "center",
|
|
59
|
+
paddingHorizontal: 24,
|
|
60
|
+
paddingTop: 8,
|
|
61
|
+
paddingBottom: 16,
|
|
62
|
+
},
|
|
63
|
+
closeButton: {
|
|
64
|
+
position: "absolute",
|
|
65
|
+
top: 8,
|
|
66
|
+
right: 16,
|
|
67
|
+
padding: 8,
|
|
68
|
+
zIndex: 1,
|
|
69
|
+
},
|
|
70
|
+
closeIcon: {
|
|
71
|
+
fontSize: 28,
|
|
72
|
+
fontWeight: "300",
|
|
73
|
+
},
|
|
74
|
+
title: {
|
|
75
|
+
marginBottom: 8,
|
|
76
|
+
textAlign: "center",
|
|
77
|
+
},
|
|
78
|
+
subtitle: {
|
|
79
|
+
textAlign: "center",
|
|
80
|
+
paddingHorizontal: 20,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscription Modal Overlay Component
|
|
3
|
+
* Handles the Modal wrapper and backdrop logic for different variants
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import {
|
|
8
|
+
View,
|
|
9
|
+
Modal,
|
|
10
|
+
StyleSheet,
|
|
11
|
+
TouchableOpacity,
|
|
12
|
+
Dimensions,
|
|
13
|
+
Platform
|
|
14
|
+
} from "react-native";
|
|
15
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
16
|
+
|
|
17
|
+
const { height: SCREEN_HEIGHT, width: SCREEN_WIDTH } = Dimensions.get("window");
|
|
18
|
+
|
|
19
|
+
export type SubscriptionModalVariant = "bottom-sheet" | "fullscreen" | "dialog";
|
|
20
|
+
|
|
21
|
+
interface SubscriptionModalOverlayProps {
|
|
22
|
+
visible: boolean;
|
|
23
|
+
onClose: () => void;
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
variant: SubscriptionModalVariant;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const SubscriptionModalOverlay: React.FC<SubscriptionModalOverlayProps> = ({
|
|
29
|
+
visible,
|
|
30
|
+
onClose,
|
|
31
|
+
children,
|
|
32
|
+
variant,
|
|
33
|
+
}) => {
|
|
34
|
+
const tokens = useAppDesignTokens();
|
|
35
|
+
|
|
36
|
+
if (__DEV__) {
|
|
37
|
+
console.log("[SubscriptionModalOverlay] Rendering variant:", variant);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const isFullScreen = variant === "fullscreen";
|
|
41
|
+
|
|
42
|
+
if (isFullScreen) {
|
|
43
|
+
return (
|
|
44
|
+
<Modal
|
|
45
|
+
visible={visible}
|
|
46
|
+
transparent={false}
|
|
47
|
+
animationType="slide"
|
|
48
|
+
onRequestClose={onClose}
|
|
49
|
+
>
|
|
50
|
+
<View style={[styles.fullscreen, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
51
|
+
{children}
|
|
52
|
+
</View>
|
|
53
|
+
</Modal>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Modal
|
|
59
|
+
visible={visible}
|
|
60
|
+
transparent
|
|
61
|
+
animationType="fade"
|
|
62
|
+
onRequestClose={onClose}
|
|
63
|
+
>
|
|
64
|
+
<View style={styles.overlay}>
|
|
65
|
+
<TouchableOpacity
|
|
66
|
+
style={styles.backdrop}
|
|
67
|
+
activeOpacity={1}
|
|
68
|
+
onPress={onClose}
|
|
69
|
+
/>
|
|
70
|
+
<View
|
|
71
|
+
style={[
|
|
72
|
+
variant === "bottom-sheet" ? styles.bottomSheet : styles.dialog,
|
|
73
|
+
{ backgroundColor: tokens.colors.surface }
|
|
74
|
+
]}
|
|
75
|
+
>
|
|
76
|
+
{children}
|
|
77
|
+
</View>
|
|
78
|
+
</View>
|
|
79
|
+
</Modal>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const styles = StyleSheet.create({
|
|
84
|
+
overlay: {
|
|
85
|
+
flex: 1,
|
|
86
|
+
justifyContent: "flex-end", // Bottom-sheet default
|
|
87
|
+
},
|
|
88
|
+
backdrop: {
|
|
89
|
+
...StyleSheet.absoluteFillObject,
|
|
90
|
+
backgroundColor: "rgba(0, 0, 0, 0.6)",
|
|
91
|
+
},
|
|
92
|
+
fullscreen: {
|
|
93
|
+
flex: 1,
|
|
94
|
+
},
|
|
95
|
+
bottomSheet: {
|
|
96
|
+
borderTopLeftRadius: 32,
|
|
97
|
+
borderTopRightRadius: 32,
|
|
98
|
+
maxHeight: SCREEN_HEIGHT * 0.9,
|
|
99
|
+
minHeight: 400,
|
|
100
|
+
width: "100%",
|
|
101
|
+
},
|
|
102
|
+
dialog: {
|
|
103
|
+
alignSelf: "center",
|
|
104
|
+
marginBottom: "auto",
|
|
105
|
+
marginTop: "auto",
|
|
106
|
+
width: Math.min(SCREEN_WIDTH * 0.9, 450),
|
|
107
|
+
maxHeight: SCREEN_HEIGHT * 0.8,
|
|
108
|
+
borderRadius: 24,
|
|
109
|
+
overflow: "hidden",
|
|
110
|
+
...Platform.select({
|
|
111
|
+
ios: {
|
|
112
|
+
shadowColor: "#000",
|
|
113
|
+
shadowOffset: { width: 0, height: 10 },
|
|
114
|
+
shadowOpacity: 0.3,
|
|
115
|
+
shadowRadius: 20,
|
|
116
|
+
},
|
|
117
|
+
android: {
|
|
118
|
+
elevation: 10,
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
121
|
+
},
|
|
122
|
+
});
|