@umituz/react-native-subscription 2.12.0 → 2.12.2
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 +2 -2
- package/src/domain/entities/paywall/PaywallMode.ts +6 -0
- package/src/index.ts +3 -0
- package/src/presentation/components/details/CreditRow.tsx +10 -16
- package/src/presentation/components/details/DetailRow.tsx +11 -17
- package/src/presentation/components/details/PremiumStatusBadge.tsx +4 -5
- package/src/presentation/components/paywall/CreditsPackageCard.tsx +100 -68
- package/src/presentation/components/paywall/PaywallFeatureItem.tsx +30 -16
- package/src/presentation/components/paywall/PaywallHeroHeader.tsx +144 -0
- package/src/presentation/components/paywall/PaywallModal.tsx +59 -59
- package/src/presentation/components/paywall/PaywallTabBar.tsx +43 -19
- package/src/presentation/components/paywall/accordion/AccordionPlanCard.tsx +5 -7
- package/src/presentation/components/paywall/accordion/PlanCardHeader.tsx +12 -22
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.2",
|
|
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",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@tanstack/react-query": "^5.0.0",
|
|
49
49
|
"@types/react": "~19.1.10",
|
|
50
|
-
"@umituz/react-native-design-system": "^2.
|
|
50
|
+
"@umituz/react-native-design-system": "^2.3.1",
|
|
51
51
|
"@umituz/react-native-firebase": "*",
|
|
52
52
|
"@umituz/react-native-legal": "*",
|
|
53
53
|
"@umituz/react-native-localization": "*",
|
package/src/index.ts
CHANGED
|
@@ -85,7 +85,10 @@ export { PaywallLegalFooter } from "./presentation/components/paywall/PaywallLeg
|
|
|
85
85
|
export {
|
|
86
86
|
PaywallModal,
|
|
87
87
|
type PaywallModalProps,
|
|
88
|
+
type PaywallTranslations,
|
|
89
|
+
type PaywallLegalUrls,
|
|
88
90
|
} from "./presentation/components/paywall/PaywallModal";
|
|
91
|
+
export type { PaywallMode } from "./domain/entities/paywall/PaywallMode";
|
|
89
92
|
|
|
90
93
|
// =============================================================================
|
|
91
94
|
// PRESENTATION LAYER - Premium Details Components
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
|
-
import { View,
|
|
8
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
8
|
+
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
9
9
|
|
|
10
10
|
export interface CreditRowProps {
|
|
11
11
|
label: string;
|
|
@@ -27,17 +27,17 @@ export const CreditRow: React.FC<CreditRowProps> = ({
|
|
|
27
27
|
return (
|
|
28
28
|
<View style={styles.container}>
|
|
29
29
|
<View style={styles.header}>
|
|
30
|
-
<
|
|
30
|
+
<AtomicText type="bodySmall" style={{ color: tokens.colors.text }}>
|
|
31
31
|
{label}
|
|
32
|
-
</
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
</AtomicText>
|
|
33
|
+
<AtomicText
|
|
34
|
+
type="bodySmall"
|
|
35
|
+
style={{
|
|
36
|
+
color: isLow ? tokens.colors.warning : tokens.colors.textSecondary,
|
|
37
|
+
}}
|
|
38
38
|
>
|
|
39
39
|
{current} / {total} {remainingLabel}
|
|
40
|
-
</
|
|
40
|
+
</AtomicText>
|
|
41
41
|
</View>
|
|
42
42
|
<View
|
|
43
43
|
style={[styles.progressBar, { backgroundColor: tokens.colors.surfaceSecondary }]}
|
|
@@ -65,12 +65,6 @@ const styles = StyleSheet.create({
|
|
|
65
65
|
justifyContent: "space-between",
|
|
66
66
|
alignItems: "center",
|
|
67
67
|
},
|
|
68
|
-
label: {
|
|
69
|
-
fontSize: 13,
|
|
70
|
-
},
|
|
71
|
-
count: {
|
|
72
|
-
fontSize: 12,
|
|
73
|
-
},
|
|
74
68
|
progressBar: {
|
|
75
69
|
height: 6,
|
|
76
70
|
borderRadius: 3,
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
|
-
import { View,
|
|
8
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
8
|
+
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
9
9
|
|
|
10
10
|
export interface DetailRowProps {
|
|
11
11
|
label: string;
|
|
@@ -22,17 +22,18 @@ export const DetailRow: React.FC<DetailRowProps> = ({
|
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
24
|
<View style={styles.container}>
|
|
25
|
-
<
|
|
25
|
+
<AtomicText type="bodyMedium" style={{ color: tokens.colors.textSecondary }}>
|
|
26
26
|
{label}
|
|
27
|
-
</
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
</AtomicText>
|
|
28
|
+
<AtomicText
|
|
29
|
+
type="bodyMedium"
|
|
30
|
+
style={{
|
|
31
|
+
color: highlight ? tokens.colors.warning : tokens.colors.text,
|
|
32
|
+
fontWeight: "500",
|
|
33
|
+
}}
|
|
33
34
|
>
|
|
34
35
|
{value}
|
|
35
|
-
</
|
|
36
|
+
</AtomicText>
|
|
36
37
|
</View>
|
|
37
38
|
);
|
|
38
39
|
};
|
|
@@ -43,11 +44,4 @@ const styles = StyleSheet.create({
|
|
|
43
44
|
justifyContent: "space-between",
|
|
44
45
|
alignItems: "center",
|
|
45
46
|
},
|
|
46
|
-
label: {
|
|
47
|
-
fontSize: 14,
|
|
48
|
-
},
|
|
49
|
-
value: {
|
|
50
|
-
fontSize: 14,
|
|
51
|
-
fontWeight: "500",
|
|
52
|
-
},
|
|
53
47
|
});
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
|
-
import { View,
|
|
8
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
8
|
+
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
9
9
|
import { SubscriptionStatusType } from "../../../domain/entities/SubscriptionStatus";
|
|
10
10
|
export type { SubscriptionStatusType };
|
|
11
11
|
|
|
@@ -48,9 +48,9 @@ export const PremiumStatusBadge: React.FC<PremiumStatusBadgeProps> = ({
|
|
|
48
48
|
|
|
49
49
|
return (
|
|
50
50
|
<View style={[styles.badge, { backgroundColor }]}>
|
|
51
|
-
<
|
|
51
|
+
<AtomicText type="labelSmall" style={[styles.badgeText, { color: tokens.colors.onPrimary }]}>
|
|
52
52
|
{label}
|
|
53
|
-
</
|
|
53
|
+
</AtomicText>
|
|
54
54
|
</View>
|
|
55
55
|
);
|
|
56
56
|
};
|
|
@@ -62,7 +62,6 @@ const styles = StyleSheet.create({
|
|
|
62
62
|
borderRadius: 4,
|
|
63
63
|
},
|
|
64
64
|
badgeText: {
|
|
65
|
-
fontSize: 12,
|
|
66
65
|
fontWeight: "600",
|
|
67
66
|
},
|
|
68
67
|
});
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Credits Package Card Component
|
|
3
|
-
*
|
|
3
|
+
* Selectable card for credit packages
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { View, StyleSheet, TouchableOpacity } from "react-native";
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import {
|
|
9
|
+
AtomicText,
|
|
10
|
+
AtomicIcon,
|
|
11
|
+
AtomicBadge,
|
|
12
|
+
useAppDesignTokens,
|
|
13
|
+
} from "@umituz/react-native-design-system";
|
|
10
14
|
import type { CreditsPackage } from "../../../domain/entities/paywall/CreditsPackage";
|
|
11
15
|
|
|
12
16
|
interface CreditsPackageCardProps {
|
|
@@ -15,94 +19,110 @@ interface CreditsPackageCardProps {
|
|
|
15
19
|
onSelect: () => void;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
export const CreditsPackageCard: React.FC<CreditsPackageCardProps> =
|
|
19
|
-
|
|
22
|
+
export const CreditsPackageCard: React.FC<CreditsPackageCardProps> = React.memo(
|
|
23
|
+
({ package: pkg, isSelected, onSelect }) => {
|
|
20
24
|
const tokens = useAppDesignTokens();
|
|
21
|
-
|
|
22
25
|
const totalCredits = pkg.credits + (pkg.bonus || 0);
|
|
23
26
|
|
|
24
27
|
return (
|
|
25
28
|
<TouchableOpacity
|
|
26
|
-
style={[
|
|
27
|
-
styles.container,
|
|
28
|
-
{
|
|
29
|
-
backgroundColor: isSelected
|
|
30
|
-
? tokens.colors.primaryLight
|
|
31
|
-
: tokens.colors.surface,
|
|
32
|
-
borderColor: isSelected
|
|
33
|
-
? tokens.colors.primary
|
|
34
|
-
: tokens.colors.border,
|
|
35
|
-
borderWidth: isSelected ? 2 : 1,
|
|
36
|
-
},
|
|
37
|
-
]}
|
|
38
29
|
onPress={onSelect}
|
|
39
|
-
activeOpacity={0.
|
|
30
|
+
activeOpacity={0.7}
|
|
31
|
+
style={styles.touchable}
|
|
40
32
|
>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
>
|
|
59
|
-
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
33
|
+
<View
|
|
34
|
+
style={[
|
|
35
|
+
styles.container,
|
|
36
|
+
{
|
|
37
|
+
backgroundColor: tokens.colors.surface,
|
|
38
|
+
borderColor: isSelected ? tokens.colors.primary : tokens.colors.border,
|
|
39
|
+
borderWidth: isSelected ? 2 : 1,
|
|
40
|
+
},
|
|
41
|
+
]}
|
|
42
|
+
>
|
|
43
|
+
{pkg.badge && (
|
|
44
|
+
<View style={styles.badgeContainer}>
|
|
45
|
+
<AtomicBadge text={pkg.badge} variant="warning" size="sm" />
|
|
46
|
+
</View>
|
|
47
|
+
)}
|
|
48
|
+
|
|
49
|
+
<View style={styles.content}>
|
|
50
|
+
<View style={styles.leftSection}>
|
|
51
|
+
<View style={styles.creditsRow}>
|
|
52
|
+
<AtomicIcon
|
|
53
|
+
name="flash"
|
|
54
|
+
size="md"
|
|
55
|
+
color={isSelected ? "primary" : "secondary"}
|
|
56
|
+
/>
|
|
57
|
+
<AtomicText
|
|
58
|
+
type="headlineSmall"
|
|
59
|
+
style={[
|
|
60
|
+
styles.credits,
|
|
61
|
+
{ color: isSelected ? tokens.colors.primary : tokens.colors.textPrimary },
|
|
62
|
+
]}
|
|
63
|
+
>
|
|
64
|
+
{totalCredits.toLocaleString()}
|
|
65
|
+
</AtomicText>
|
|
66
|
+
</View>
|
|
67
|
+
|
|
68
|
+
{(pkg.bonus ?? 0) > 0 && (
|
|
69
|
+
<View style={styles.bonusContainer}>
|
|
70
|
+
<AtomicIcon name="gift-outline" size="sm" color="success" />
|
|
71
|
+
<AtomicText
|
|
72
|
+
type="bodySmall"
|
|
73
|
+
style={[styles.bonus, { color: tokens.colors.success }]}
|
|
74
|
+
>
|
|
75
|
+
+{pkg.bonus}
|
|
76
|
+
</AtomicText>
|
|
77
|
+
</View>
|
|
78
|
+
)}
|
|
79
|
+
|
|
80
|
+
{pkg.description && (
|
|
81
|
+
<AtomicText
|
|
82
|
+
type="bodySmall"
|
|
83
|
+
style={{ color: tokens.colors.textSecondary }}
|
|
84
|
+
>
|
|
85
|
+
{pkg.description}
|
|
86
|
+
</AtomicText>
|
|
87
|
+
)}
|
|
88
|
+
</View>
|
|
89
|
+
|
|
90
|
+
<View style={styles.rightSection}>
|
|
70
91
|
<AtomicText
|
|
71
|
-
type="
|
|
72
|
-
style={
|
|
92
|
+
type="titleLarge"
|
|
93
|
+
style={[
|
|
94
|
+
styles.price,
|
|
95
|
+
{ color: isSelected ? tokens.colors.primary : tokens.colors.textPrimary },
|
|
96
|
+
]}
|
|
73
97
|
>
|
|
74
|
-
{pkg.
|
|
98
|
+
{pkg.currency}{pkg.price.toFixed(2)}
|
|
75
99
|
</AtomicText>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
type="titleLarge"
|
|
81
|
-
style={[styles.price, { color: tokens.colors.primary }]}
|
|
82
|
-
>
|
|
83
|
-
{pkg.currency} {pkg.price.toFixed(2)}
|
|
84
|
-
</AtomicText>
|
|
100
|
+
{isSelected && (
|
|
101
|
+
<AtomicIcon name="checkmark-circle" size="md" color="primary" />
|
|
102
|
+
)}
|
|
103
|
+
</View>
|
|
85
104
|
</View>
|
|
86
105
|
</View>
|
|
87
106
|
</TouchableOpacity>
|
|
88
107
|
);
|
|
89
|
-
}
|
|
108
|
+
}
|
|
109
|
+
);
|
|
90
110
|
|
|
91
111
|
CreditsPackageCard.displayName = "CreditsPackageCard";
|
|
92
112
|
|
|
93
113
|
const styles = StyleSheet.create({
|
|
114
|
+
touchable: {
|
|
115
|
+
marginBottom: 12,
|
|
116
|
+
},
|
|
94
117
|
container: {
|
|
95
118
|
borderRadius: 16,
|
|
96
|
-
padding:
|
|
119
|
+
padding: 16,
|
|
97
120
|
position: "relative",
|
|
98
121
|
},
|
|
99
|
-
|
|
122
|
+
badgeContainer: {
|
|
100
123
|
position: "absolute",
|
|
101
124
|
top: -10,
|
|
102
|
-
right:
|
|
103
|
-
paddingHorizontal: 10,
|
|
104
|
-
paddingVertical: 4,
|
|
105
|
-
borderRadius: 8,
|
|
125
|
+
right: 16,
|
|
106
126
|
},
|
|
107
127
|
content: {
|
|
108
128
|
flexDirection: "row",
|
|
@@ -111,19 +131,31 @@ const styles = StyleSheet.create({
|
|
|
111
131
|
},
|
|
112
132
|
leftSection: {
|
|
113
133
|
flex: 1,
|
|
134
|
+
marginRight: 16,
|
|
135
|
+
},
|
|
136
|
+
creditsRow: {
|
|
137
|
+
flexDirection: "row",
|
|
138
|
+
alignItems: "center",
|
|
139
|
+
marginBottom: 4,
|
|
114
140
|
},
|
|
115
141
|
credits: {
|
|
116
142
|
fontWeight: "700",
|
|
143
|
+
marginLeft: 8,
|
|
144
|
+
},
|
|
145
|
+
bonusContainer: {
|
|
146
|
+
flexDirection: "row",
|
|
147
|
+
alignItems: "center",
|
|
117
148
|
marginBottom: 4,
|
|
118
149
|
},
|
|
119
150
|
bonus: {
|
|
120
151
|
fontWeight: "600",
|
|
121
|
-
|
|
152
|
+
marginLeft: 4,
|
|
122
153
|
},
|
|
123
154
|
rightSection: {
|
|
124
155
|
alignItems: "flex-end",
|
|
125
156
|
},
|
|
126
157
|
price: {
|
|
127
158
|
fontWeight: "700",
|
|
159
|
+
marginBottom: 4,
|
|
128
160
|
},
|
|
129
161
|
});
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Paywall Feature Item Component
|
|
3
|
-
* Single
|
|
3
|
+
* Single feature in the features list
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React, { useMemo } from "react";
|
|
7
7
|
import { View, StyleSheet } from "react-native";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
AtomicText,
|
|
10
|
+
AtomicIcon,
|
|
11
|
+
useAppDesignTokens,
|
|
12
|
+
useResponsive,
|
|
13
|
+
} from "@umituz/react-native-design-system";
|
|
9
14
|
|
|
10
15
|
interface PaywallFeatureItemProps {
|
|
11
16
|
icon: string;
|
|
@@ -19,33 +24,36 @@ export const PaywallFeatureItem: React.FC<PaywallFeatureItemProps> = React.memo(
|
|
|
19
24
|
|
|
20
25
|
const styles = useMemo(() => createStyles(spacingMultiplier), [spacingMultiplier]);
|
|
21
26
|
const fontSize = getFontSize(15);
|
|
22
|
-
const
|
|
23
|
-
const iconSize = getFontSize(20);
|
|
27
|
+
const iconSize = getFontSize(18);
|
|
24
28
|
|
|
25
|
-
// Pass icon name directly to AtomicIcon (which uses Ionicons)
|
|
26
|
-
// Do NOT capitalize, as Ionicons names are lowercase/kebab-case.
|
|
27
29
|
const iconName = useMemo(() => {
|
|
28
|
-
if (!icon) return "
|
|
30
|
+
if (!icon) return "checkmark-circle";
|
|
29
31
|
return icon;
|
|
30
32
|
}, [icon]);
|
|
31
33
|
|
|
32
34
|
return (
|
|
33
35
|
<View style={styles.featureItem}>
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
<View
|
|
37
|
+
style={[
|
|
38
|
+
styles.iconContainer,
|
|
39
|
+
{ backgroundColor: tokens.colors.primaryLight },
|
|
40
|
+
]}
|
|
41
|
+
>
|
|
42
|
+
<AtomicIcon
|
|
43
|
+
name={iconName}
|
|
44
|
+
customSize={iconSize}
|
|
45
|
+
customColor={tokens.colors.primary}
|
|
46
|
+
/>
|
|
47
|
+
</View>
|
|
40
48
|
<AtomicText
|
|
41
49
|
type="bodyMedium"
|
|
42
|
-
style={[styles.featureText, { color: tokens.colors.textPrimary, fontSize
|
|
50
|
+
style={[styles.featureText, { color: tokens.colors.textPrimary, fontSize }]}
|
|
43
51
|
>
|
|
44
52
|
{text}
|
|
45
53
|
</AtomicText>
|
|
46
54
|
</View>
|
|
47
55
|
);
|
|
48
|
-
}
|
|
56
|
+
}
|
|
49
57
|
);
|
|
50
58
|
|
|
51
59
|
PaywallFeatureItem.displayName = "PaywallFeatureItem";
|
|
@@ -56,10 +64,16 @@ const createStyles = (spacingMult: number) =>
|
|
|
56
64
|
flexDirection: "row",
|
|
57
65
|
alignItems: "center",
|
|
58
66
|
},
|
|
59
|
-
|
|
67
|
+
iconContainer: {
|
|
68
|
+
width: 32 * spacingMult,
|
|
69
|
+
height: 32 * spacingMult,
|
|
70
|
+
borderRadius: 16 * spacingMult,
|
|
71
|
+
justifyContent: "center",
|
|
72
|
+
alignItems: "center",
|
|
60
73
|
marginRight: 12 * spacingMult,
|
|
61
74
|
},
|
|
62
75
|
featureText: {
|
|
63
76
|
flex: 1,
|
|
77
|
+
fontWeight: "500",
|
|
64
78
|
},
|
|
65
79
|
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paywall Hero Header Component
|
|
3
|
+
* Header with gradient background - theme aware
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { View, StyleSheet, TouchableOpacity } from "react-native";
|
|
8
|
+
import { LinearGradient } from "expo-linear-gradient";
|
|
9
|
+
import {
|
|
10
|
+
AtomicText,
|
|
11
|
+
AtomicIcon,
|
|
12
|
+
useDesignSystemTheme,
|
|
13
|
+
useAppDesignTokens,
|
|
14
|
+
} from "@umituz/react-native-design-system";
|
|
15
|
+
|
|
16
|
+
interface PaywallHeroHeaderProps {
|
|
17
|
+
title: string;
|
|
18
|
+
subtitle?: string;
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const PaywallHeroHeader: React.FC<PaywallHeroHeaderProps> = React.memo(
|
|
23
|
+
({ title, subtitle, onClose }) => {
|
|
24
|
+
const tokens = useAppDesignTokens();
|
|
25
|
+
const { themeMode } = useDesignSystemTheme();
|
|
26
|
+
const isDark = themeMode === "dark";
|
|
27
|
+
|
|
28
|
+
const gradientColors: readonly [string, string, string] = isDark
|
|
29
|
+
? [tokens.colors.background, tokens.colors.surfaceSecondary, tokens.colors.surface]
|
|
30
|
+
: [tokens.colors.primary, tokens.colors.primaryDark, tokens.colors.primary];
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<LinearGradient
|
|
34
|
+
colors={gradientColors}
|
|
35
|
+
start={{ x: 0, y: 0 }}
|
|
36
|
+
end={{ x: 1, y: 1 }}
|
|
37
|
+
style={styles.container}
|
|
38
|
+
>
|
|
39
|
+
<View style={[styles.decorativeCircle, styles.circle1]} />
|
|
40
|
+
<View style={[styles.decorativeCircle, styles.circle2]} />
|
|
41
|
+
|
|
42
|
+
<TouchableOpacity
|
|
43
|
+
onPress={onClose}
|
|
44
|
+
style={[
|
|
45
|
+
styles.closeButton,
|
|
46
|
+
{
|
|
47
|
+
backgroundColor: isDark
|
|
48
|
+
? tokens.colors.surfaceSecondary
|
|
49
|
+
: tokens.colors.onPrimary,
|
|
50
|
+
},
|
|
51
|
+
]}
|
|
52
|
+
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
|
|
53
|
+
>
|
|
54
|
+
<AtomicIcon
|
|
55
|
+
name="close-outline"
|
|
56
|
+
size="md"
|
|
57
|
+
customColor={isDark ? tokens.colors.textPrimary : tokens.colors.primary}
|
|
58
|
+
/>
|
|
59
|
+
</TouchableOpacity>
|
|
60
|
+
|
|
61
|
+
<View style={styles.content}>
|
|
62
|
+
<AtomicText
|
|
63
|
+
type="headlineLarge"
|
|
64
|
+
style={[styles.title, { color: tokens.colors.onPrimary }]}
|
|
65
|
+
>
|
|
66
|
+
{title}
|
|
67
|
+
</AtomicText>
|
|
68
|
+
{subtitle && (
|
|
69
|
+
<AtomicText
|
|
70
|
+
type="bodyLarge"
|
|
71
|
+
style={[styles.subtitle, { color: tokens.colors.onPrimary }]}
|
|
72
|
+
>
|
|
73
|
+
{subtitle}
|
|
74
|
+
</AtomicText>
|
|
75
|
+
)}
|
|
76
|
+
</View>
|
|
77
|
+
|
|
78
|
+
<View style={[styles.wave, { backgroundColor: tokens.colors.background }]} />
|
|
79
|
+
</LinearGradient>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
PaywallHeroHeader.displayName = "PaywallHeroHeader";
|
|
85
|
+
|
|
86
|
+
const styles = StyleSheet.create({
|
|
87
|
+
container: {
|
|
88
|
+
paddingTop: 60,
|
|
89
|
+
paddingBottom: 40,
|
|
90
|
+
paddingHorizontal: 24,
|
|
91
|
+
position: "relative",
|
|
92
|
+
overflow: "hidden",
|
|
93
|
+
},
|
|
94
|
+
decorativeCircle: {
|
|
95
|
+
position: "absolute",
|
|
96
|
+
borderRadius: 9999,
|
|
97
|
+
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
|
98
|
+
},
|
|
99
|
+
circle1: {
|
|
100
|
+
width: 200,
|
|
101
|
+
height: 200,
|
|
102
|
+
top: -100,
|
|
103
|
+
right: -50,
|
|
104
|
+
},
|
|
105
|
+
circle2: {
|
|
106
|
+
width: 150,
|
|
107
|
+
height: 150,
|
|
108
|
+
bottom: -75,
|
|
109
|
+
left: -40,
|
|
110
|
+
},
|
|
111
|
+
closeButton: {
|
|
112
|
+
position: "absolute",
|
|
113
|
+
top: 50,
|
|
114
|
+
right: 20,
|
|
115
|
+
width: 36,
|
|
116
|
+
height: 36,
|
|
117
|
+
borderRadius: 18,
|
|
118
|
+
justifyContent: "center",
|
|
119
|
+
alignItems: "center",
|
|
120
|
+
zIndex: 10,
|
|
121
|
+
},
|
|
122
|
+
content: {
|
|
123
|
+
alignItems: "center",
|
|
124
|
+
zIndex: 1,
|
|
125
|
+
},
|
|
126
|
+
title: {
|
|
127
|
+
fontWeight: "700",
|
|
128
|
+
textAlign: "center",
|
|
129
|
+
marginBottom: 8,
|
|
130
|
+
},
|
|
131
|
+
subtitle: {
|
|
132
|
+
textAlign: "center",
|
|
133
|
+
opacity: 0.9,
|
|
134
|
+
},
|
|
135
|
+
wave: {
|
|
136
|
+
position: "absolute",
|
|
137
|
+
bottom: -1,
|
|
138
|
+
left: 0,
|
|
139
|
+
right: 0,
|
|
140
|
+
height: 30,
|
|
141
|
+
borderTopLeftRadius: 30,
|
|
142
|
+
borderTopRightRadius: 30,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Paywall Modal Component
|
|
3
|
-
*
|
|
3
|
+
* Mode-based paywall: subscription, credits, or hybrid
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
@@ -8,76 +8,69 @@ import { View, StyleSheet } from "react-native";
|
|
|
8
8
|
import { BaseModal } from "@umituz/react-native-design-system";
|
|
9
9
|
import type { PurchasesPackage } from "react-native-purchases";
|
|
10
10
|
import { usePaywall } from "../../hooks/usePaywall";
|
|
11
|
-
import {
|
|
11
|
+
import { PaywallHeroHeader } from "./PaywallHeroHeader";
|
|
12
12
|
import { PaywallTabBar } from "./PaywallTabBar";
|
|
13
13
|
import { CreditsTabContent } from "./CreditsTabContent";
|
|
14
14
|
import { SubscriptionTabContent } from "./SubscriptionTabContent";
|
|
15
15
|
import type { PaywallTabType } from "../../../domain/entities/paywall/PaywallTab";
|
|
16
|
+
import type { PaywallMode } from "../../../domain/entities/paywall/PaywallMode";
|
|
16
17
|
import type { CreditsPackage } from "../../../domain/entities/paywall/CreditsPackage";
|
|
17
18
|
|
|
18
|
-
export interface PaywallModalStyles {
|
|
19
|
-
headerTopPadding?: number;
|
|
20
|
-
contentHorizontalPadding?: number;
|
|
21
|
-
contentBottomPadding?: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
19
|
export interface PaywallModalProps {
|
|
25
20
|
visible: boolean;
|
|
26
21
|
onClose: () => void;
|
|
22
|
+
mode: PaywallMode;
|
|
27
23
|
initialTab?: PaywallTabType;
|
|
28
|
-
creditsPackages
|
|
29
|
-
subscriptionPackages
|
|
30
|
-
currentCredits
|
|
24
|
+
creditsPackages?: CreditsPackage[];
|
|
25
|
+
subscriptionPackages?: PurchasesPackage[];
|
|
26
|
+
currentCredits?: number;
|
|
31
27
|
requiredCredits?: number;
|
|
32
|
-
onCreditsPurchase
|
|
33
|
-
onSubscriptionPurchase
|
|
28
|
+
onCreditsPurchase?: (packageId: string) => Promise<void>;
|
|
29
|
+
onSubscriptionPurchase?: (pkg: PurchasesPackage) => Promise<void>;
|
|
34
30
|
onRestore?: () => Promise<void>;
|
|
35
31
|
subscriptionFeatures?: Array<{ icon: string; text: string }>;
|
|
36
32
|
isLoading?: boolean;
|
|
33
|
+
translations: PaywallTranslations;
|
|
34
|
+
legalUrls?: PaywallLegalUrls;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface PaywallTranslations {
|
|
37
38
|
title: string;
|
|
38
39
|
subtitle: string;
|
|
39
|
-
creditsTabLabel
|
|
40
|
-
subscriptionTabLabel
|
|
40
|
+
creditsTabLabel?: string;
|
|
41
|
+
subscriptionTabLabel?: string;
|
|
41
42
|
purchaseButtonText: string;
|
|
42
|
-
subscribeButtonText
|
|
43
|
+
subscribeButtonText?: string;
|
|
43
44
|
restoreButtonText: string;
|
|
44
45
|
loadingText: string;
|
|
45
46
|
emptyText: string;
|
|
46
47
|
processingText: string;
|
|
47
|
-
privacyUrl?: string;
|
|
48
|
-
termsUrl?: string;
|
|
49
48
|
privacyText?: string;
|
|
50
49
|
termsOfServiceText?: string;
|
|
51
50
|
}
|
|
52
51
|
|
|
52
|
+
export interface PaywallLegalUrls {
|
|
53
|
+
privacyUrl?: string;
|
|
54
|
+
termsUrl?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
53
57
|
export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
|
|
54
58
|
const {
|
|
55
59
|
visible,
|
|
56
60
|
onClose,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
mode,
|
|
62
|
+
initialTab = mode === "credits" ? "credits" : "subscription",
|
|
63
|
+
creditsPackages = [],
|
|
64
|
+
subscriptionPackages = [],
|
|
65
|
+
currentCredits = 0,
|
|
61
66
|
requiredCredits,
|
|
62
67
|
onCreditsPurchase,
|
|
63
68
|
onSubscriptionPurchase,
|
|
64
69
|
onRestore,
|
|
65
70
|
subscriptionFeatures = [],
|
|
66
71
|
isLoading = false,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
creditsTabLabel,
|
|
70
|
-
subscriptionTabLabel,
|
|
71
|
-
purchaseButtonText,
|
|
72
|
-
subscribeButtonText,
|
|
73
|
-
privacyUrl,
|
|
74
|
-
termsUrl,
|
|
75
|
-
privacyText,
|
|
76
|
-
termsOfServiceText,
|
|
77
|
-
restoreButtonText,
|
|
78
|
-
loadingText,
|
|
79
|
-
emptyText,
|
|
80
|
-
processingText,
|
|
72
|
+
translations,
|
|
73
|
+
legalUrls = {},
|
|
81
74
|
} = props;
|
|
82
75
|
|
|
83
76
|
const {
|
|
@@ -91,28 +84,34 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
|
|
|
91
84
|
handleSubscriptionPurchase,
|
|
92
85
|
} = usePaywall({
|
|
93
86
|
initialTab,
|
|
94
|
-
onCreditsPurchase,
|
|
95
|
-
onSubscriptionPurchase,
|
|
87
|
+
onCreditsPurchase: onCreditsPurchase ?? (() => Promise.resolve()),
|
|
88
|
+
onSubscriptionPurchase: onSubscriptionPurchase ?? (() => Promise.resolve()),
|
|
96
89
|
});
|
|
97
90
|
|
|
91
|
+
const showTabs = mode === "hybrid";
|
|
92
|
+
const showCredits = mode === "credits" || (mode === "hybrid" && activeTab === "credits");
|
|
93
|
+
const showSubscription = mode === "subscription" || (mode === "hybrid" && activeTab === "subscription");
|
|
94
|
+
|
|
98
95
|
return (
|
|
99
96
|
<BaseModal visible={visible} onClose={onClose}>
|
|
100
97
|
<View style={styles.container}>
|
|
101
|
-
<
|
|
102
|
-
title={title}
|
|
103
|
-
subtitle={subtitle}
|
|
98
|
+
<PaywallHeroHeader
|
|
99
|
+
title={translations.title}
|
|
100
|
+
subtitle={translations.subtitle}
|
|
104
101
|
onClose={onClose}
|
|
105
102
|
/>
|
|
106
103
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
104
|
+
{showTabs && (
|
|
105
|
+
<PaywallTabBar
|
|
106
|
+
activeTab={activeTab}
|
|
107
|
+
onTabChange={handleTabChange}
|
|
108
|
+
creditsLabel={translations.creditsTabLabel}
|
|
109
|
+
subscriptionLabel={translations.subscriptionTabLabel}
|
|
110
|
+
/>
|
|
111
|
+
)}
|
|
113
112
|
|
|
114
113
|
<View style={styles.tabContent}>
|
|
115
|
-
{
|
|
114
|
+
{showCredits && (
|
|
116
115
|
<CreditsTabContent
|
|
117
116
|
packages={creditsPackages}
|
|
118
117
|
selectedPackageId={selectedCreditsPackageId}
|
|
@@ -121,9 +120,10 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
|
|
|
121
120
|
currentCredits={currentCredits}
|
|
122
121
|
requiredCredits={requiredCredits}
|
|
123
122
|
isLoading={isLoading}
|
|
124
|
-
purchaseButtonText={purchaseButtonText}
|
|
123
|
+
purchaseButtonText={translations.purchaseButtonText}
|
|
125
124
|
/>
|
|
126
|
-
)
|
|
125
|
+
)}
|
|
126
|
+
{showSubscription && (
|
|
127
127
|
<SubscriptionTabContent
|
|
128
128
|
packages={subscriptionPackages}
|
|
129
129
|
selectedPackage={selectedSubscriptionPkg}
|
|
@@ -131,16 +131,16 @@ export const PaywallModal: React.FC<PaywallModalProps> = React.memo((props) => {
|
|
|
131
131
|
onPurchase={handleSubscriptionPurchase}
|
|
132
132
|
features={subscriptionFeatures}
|
|
133
133
|
isLoading={isLoading}
|
|
134
|
-
purchaseButtonText={subscribeButtonText}
|
|
135
|
-
processingText={processingText}
|
|
136
|
-
restoreButtonText={restoreButtonText}
|
|
137
|
-
loadingText={loadingText}
|
|
138
|
-
emptyText={emptyText}
|
|
134
|
+
purchaseButtonText={translations.subscribeButtonText ?? translations.purchaseButtonText}
|
|
135
|
+
processingText={translations.processingText}
|
|
136
|
+
restoreButtonText={translations.restoreButtonText}
|
|
137
|
+
loadingText={translations.loadingText}
|
|
138
|
+
emptyText={translations.emptyText}
|
|
139
139
|
onRestore={onRestore}
|
|
140
|
-
privacyUrl={privacyUrl}
|
|
141
|
-
termsUrl={termsUrl}
|
|
142
|
-
privacyText={privacyText}
|
|
143
|
-
termsOfServiceText={termsOfServiceText}
|
|
140
|
+
privacyUrl={legalUrls.privacyUrl}
|
|
141
|
+
termsUrl={legalUrls.termsUrl}
|
|
142
|
+
privacyText={translations.privacyText}
|
|
143
|
+
termsOfServiceText={translations.termsOfServiceText}
|
|
144
144
|
/>
|
|
145
145
|
)}
|
|
146
146
|
</View>
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Paywall Tab Bar Component
|
|
3
|
-
*
|
|
3
|
+
* Segmented control for paywall tabs
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
|
-
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
8
|
-
import { AtomicText } from "@umituz/react-native-design-system";
|
|
9
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
7
|
+
import { View, TouchableOpacity, StyleSheet, Animated } from "react-native";
|
|
8
|
+
import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
10
9
|
import type { PaywallTabType } from "../../../domain/entities/paywall/PaywallTab";
|
|
11
10
|
|
|
12
11
|
interface PaywallTabBarProps {
|
|
@@ -24,6 +23,18 @@ export const PaywallTabBar: React.FC<PaywallTabBarProps> = React.memo(
|
|
|
24
23
|
subscriptionLabel = "Subscription",
|
|
25
24
|
}) => {
|
|
26
25
|
const tokens = useAppDesignTokens();
|
|
26
|
+
const animatedValue = React.useRef(
|
|
27
|
+
new Animated.Value(activeTab === "credits" ? 0 : 1)
|
|
28
|
+
).current;
|
|
29
|
+
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
Animated.spring(animatedValue, {
|
|
32
|
+
toValue: activeTab === "credits" ? 0 : 1,
|
|
33
|
+
useNativeDriver: false,
|
|
34
|
+
tension: 68,
|
|
35
|
+
friction: 12,
|
|
36
|
+
}).start();
|
|
37
|
+
}, [activeTab, animatedValue]);
|
|
27
38
|
|
|
28
39
|
const renderTab = (tab: PaywallTabType, label: string) => {
|
|
29
40
|
const isActive = activeTab === tab;
|
|
@@ -31,25 +42,16 @@ export const PaywallTabBar: React.FC<PaywallTabBarProps> = React.memo(
|
|
|
31
42
|
return (
|
|
32
43
|
<TouchableOpacity
|
|
33
44
|
key={tab}
|
|
34
|
-
style={
|
|
35
|
-
styles.tab,
|
|
36
|
-
{
|
|
37
|
-
backgroundColor: isActive
|
|
38
|
-
? tokens.colors.primary
|
|
39
|
-
: tokens.colors.surfaceSecondary,
|
|
40
|
-
},
|
|
41
|
-
]}
|
|
45
|
+
style={styles.tab}
|
|
42
46
|
onPress={() => onTabChange(tab)}
|
|
43
|
-
activeOpacity={0.
|
|
47
|
+
activeOpacity={0.7}
|
|
44
48
|
>
|
|
45
49
|
<AtomicText
|
|
46
50
|
type="labelLarge"
|
|
47
51
|
style={[
|
|
48
52
|
styles.tabText,
|
|
49
53
|
{
|
|
50
|
-
color: isActive
|
|
51
|
-
? tokens.colors.onPrimary
|
|
52
|
-
: tokens.colors.textSecondary,
|
|
54
|
+
color: isActive ? tokens.colors.primary : tokens.colors.textSecondary,
|
|
53
55
|
},
|
|
54
56
|
]}
|
|
55
57
|
>
|
|
@@ -59,6 +61,11 @@ export const PaywallTabBar: React.FC<PaywallTabBarProps> = React.memo(
|
|
|
59
61
|
);
|
|
60
62
|
};
|
|
61
63
|
|
|
64
|
+
const indicatorTranslateX = animatedValue.interpolate({
|
|
65
|
+
inputRange: [0, 1],
|
|
66
|
+
outputRange: ["0%", "50%"],
|
|
67
|
+
});
|
|
68
|
+
|
|
62
69
|
return (
|
|
63
70
|
<View
|
|
64
71
|
style={[
|
|
@@ -66,11 +73,20 @@ export const PaywallTabBar: React.FC<PaywallTabBarProps> = React.memo(
|
|
|
66
73
|
{ backgroundColor: tokens.colors.surfaceSecondary },
|
|
67
74
|
]}
|
|
68
75
|
>
|
|
76
|
+
<Animated.View
|
|
77
|
+
style={[
|
|
78
|
+
styles.indicator,
|
|
79
|
+
{
|
|
80
|
+
backgroundColor: tokens.colors.surface,
|
|
81
|
+
left: indicatorTranslateX,
|
|
82
|
+
},
|
|
83
|
+
]}
|
|
84
|
+
/>
|
|
69
85
|
{renderTab("credits", creditsLabel)}
|
|
70
86
|
{renderTab("subscription", subscriptionLabel)}
|
|
71
87
|
</View>
|
|
72
88
|
);
|
|
73
|
-
}
|
|
89
|
+
}
|
|
74
90
|
);
|
|
75
91
|
|
|
76
92
|
PaywallTabBar.displayName = "PaywallTabBar";
|
|
@@ -82,13 +98,21 @@ const styles = StyleSheet.create({
|
|
|
82
98
|
padding: 4,
|
|
83
99
|
marginHorizontal: 24,
|
|
84
100
|
marginBottom: 16,
|
|
101
|
+
position: "relative",
|
|
102
|
+
height: 44,
|
|
103
|
+
},
|
|
104
|
+
indicator: {
|
|
105
|
+
position: "absolute",
|
|
106
|
+
top: 4,
|
|
107
|
+
bottom: 4,
|
|
108
|
+
width: "48%",
|
|
109
|
+
borderRadius: 8,
|
|
85
110
|
},
|
|
86
111
|
tab: {
|
|
87
112
|
flex: 1,
|
|
88
|
-
paddingVertical: 12,
|
|
89
|
-
borderRadius: 8,
|
|
90
113
|
alignItems: "center",
|
|
91
114
|
justifyContent: "center",
|
|
115
|
+
zIndex: 1,
|
|
92
116
|
},
|
|
93
117
|
tabText: {
|
|
94
118
|
fontWeight: "600",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Accordion Plan Card
|
|
3
|
-
* Expandable subscription plan card
|
|
3
|
+
* Expandable subscription plan card
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React, { useCallback, useMemo } from "react";
|
|
@@ -41,7 +41,6 @@ export const AccordionPlanCard: React.FC<AccordionPlanCardProps> = React.memo(
|
|
|
41
41
|
: null;
|
|
42
42
|
|
|
43
43
|
const title = pkg.product.title || t(`paywall.period.${periodLabel}`);
|
|
44
|
-
const displayPrice = price;
|
|
45
44
|
|
|
46
45
|
const handleHeaderPress = useCallback(() => {
|
|
47
46
|
onSelect();
|
|
@@ -53,11 +52,9 @@ export const AccordionPlanCard: React.FC<AccordionPlanCardProps> = React.memo(
|
|
|
53
52
|
const containerStyle: StyleProp<ViewStyle> = [
|
|
54
53
|
styles.container,
|
|
55
54
|
{
|
|
56
|
-
borderColor: isSelected
|
|
57
|
-
? tokens.colors.primary
|
|
58
|
-
: tokens.colors.borderLight,
|
|
59
|
-
borderWidth: isSelected ? 2 : 1,
|
|
60
55
|
backgroundColor: tokens.colors.surface,
|
|
56
|
+
borderColor: isSelected ? tokens.colors.primary : tokens.colors.border,
|
|
57
|
+
borderWidth: isSelected ? 2 : 1,
|
|
61
58
|
},
|
|
62
59
|
];
|
|
63
60
|
|
|
@@ -65,7 +62,7 @@ export const AccordionPlanCard: React.FC<AccordionPlanCardProps> = React.memo(
|
|
|
65
62
|
<View style={containerStyle}>
|
|
66
63
|
<PlanCardHeader
|
|
67
64
|
title={title}
|
|
68
|
-
price={
|
|
65
|
+
price={price}
|
|
69
66
|
creditAmount={creditAmount}
|
|
70
67
|
isSelected={isSelected}
|
|
71
68
|
isExpanded={isExpanded}
|
|
@@ -96,5 +93,6 @@ const createStyles = (spacingMult: number) =>
|
|
|
96
93
|
container: {
|
|
97
94
|
borderRadius: 16 * spacingMult,
|
|
98
95
|
marginBottom: 12 * spacingMult,
|
|
96
|
+
overflow: "hidden",
|
|
99
97
|
},
|
|
100
98
|
});
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plan Card Header
|
|
3
|
-
*
|
|
3
|
+
* Header for accordion subscription card
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React, { useMemo } from "react";
|
|
7
7
|
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
8
8
|
import {
|
|
9
9
|
AtomicText,
|
|
10
|
+
AtomicIcon,
|
|
10
11
|
useAppDesignTokens,
|
|
11
12
|
withAlpha,
|
|
12
13
|
useResponsive,
|
|
@@ -47,19 +48,13 @@ export const PlanCardHeader: React.FC<PlanCardHeaderProps> = ({
|
|
|
47
48
|
style={[
|
|
48
49
|
styles.radio,
|
|
49
50
|
{
|
|
50
|
-
borderColor: isSelected
|
|
51
|
-
|
|
52
|
-
: tokens.colors.border,
|
|
51
|
+
borderColor: isSelected ? tokens.colors.primary : tokens.colors.border,
|
|
52
|
+
backgroundColor: isSelected ? tokens.colors.primary : "transparent",
|
|
53
53
|
},
|
|
54
54
|
]}
|
|
55
55
|
>
|
|
56
56
|
{isSelected && (
|
|
57
|
-
<
|
|
58
|
-
style={[
|
|
59
|
-
styles.radioInner,
|
|
60
|
-
{ backgroundColor: tokens.colors.primary },
|
|
61
|
-
]}
|
|
62
|
-
/>
|
|
57
|
+
<AtomicIcon name="checkmark" customSize={14} customColor={tokens.colors.onPrimary} />
|
|
63
58
|
)}
|
|
64
59
|
</View>
|
|
65
60
|
|
|
@@ -80,15 +75,17 @@ export const PlanCardHeader: React.FC<PlanCardHeaderProps> = ({
|
|
|
80
75
|
},
|
|
81
76
|
]}
|
|
82
77
|
>
|
|
78
|
+
<AtomicIcon name="flash" customSize={creditFontSize} customColor={tokens.colors.primary} />
|
|
83
79
|
<AtomicText
|
|
84
80
|
type="labelSmall"
|
|
85
81
|
style={{
|
|
86
82
|
color: tokens.colors.primary,
|
|
87
83
|
fontWeight: "700",
|
|
88
84
|
fontSize: creditFontSize,
|
|
85
|
+
marginLeft: 4,
|
|
89
86
|
}}
|
|
90
87
|
>
|
|
91
|
-
{creditAmount} {t("paywall.credits")
|
|
88
|
+
{creditAmount} {t("paywall.credits")}
|
|
92
89
|
</AtomicText>
|
|
93
90
|
</View>
|
|
94
91
|
)}
|
|
@@ -98,10 +95,7 @@ export const PlanCardHeader: React.FC<PlanCardHeaderProps> = ({
|
|
|
98
95
|
<View style={styles.rightSection}>
|
|
99
96
|
<AtomicText
|
|
100
97
|
type="titleMedium"
|
|
101
|
-
style={{
|
|
102
|
-
color: tokens.colors.textPrimary,
|
|
103
|
-
fontWeight: "700",
|
|
104
|
-
}}
|
|
98
|
+
style={{ color: tokens.colors.textPrimary, fontWeight: "700" }}
|
|
105
99
|
>
|
|
106
100
|
{price}
|
|
107
101
|
</AtomicText>
|
|
@@ -113,7 +107,6 @@ export const PlanCardHeader: React.FC<PlanCardHeaderProps> = ({
|
|
|
113
107
|
|
|
114
108
|
const createStyles = (spacingMult: number, touchTarget: number) => {
|
|
115
109
|
const radioSize = Math.max(touchTarget * 0.4, 22);
|
|
116
|
-
const radioInnerSize = radioSize * 0.55;
|
|
117
110
|
|
|
118
111
|
return StyleSheet.create({
|
|
119
112
|
container: {
|
|
@@ -140,17 +133,14 @@ const createStyles = (spacingMult: number, touchTarget: number) => {
|
|
|
140
133
|
justifyContent: "center",
|
|
141
134
|
marginRight: 12 * spacingMult,
|
|
142
135
|
},
|
|
143
|
-
radioInner: {
|
|
144
|
-
width: radioInnerSize,
|
|
145
|
-
height: radioInnerSize,
|
|
146
|
-
borderRadius: radioInnerSize / 2,
|
|
147
|
-
},
|
|
148
136
|
textContainer: {
|
|
149
137
|
flex: 1,
|
|
150
138
|
gap: 6 * spacingMult,
|
|
151
139
|
},
|
|
152
140
|
creditBadge: {
|
|
153
|
-
|
|
141
|
+
flexDirection: "row",
|
|
142
|
+
alignItems: "center",
|
|
143
|
+
paddingHorizontal: 8 * spacingMult,
|
|
154
144
|
paddingVertical: 4 * spacingMult,
|
|
155
145
|
borderRadius: 12 * spacingMult,
|
|
156
146
|
borderWidth: 1,
|