@umituz/react-native-subscription 2.12.1 → 2.12.3

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.
Files changed (45) hide show
  1. package/package.json +3 -3
  2. package/src/domains/index.ts +5 -0
  3. package/src/domains/paywall/components/CreditCard.tsx +117 -0
  4. package/src/domains/paywall/components/FeatureItem.tsx +50 -0
  5. package/src/domains/paywall/components/FeatureList.tsx +34 -0
  6. package/src/domains/paywall/components/PaywallFooter.tsx +98 -0
  7. package/src/domains/paywall/components/PaywallHeader.tsx +115 -0
  8. package/src/domains/paywall/components/PaywallModal.tsx +187 -0
  9. package/src/domains/paywall/components/PaywallTabBar.tsx +102 -0
  10. package/src/domains/paywall/components/PlanCard.tsx +124 -0
  11. package/src/domains/paywall/components/index.ts +14 -0
  12. package/src/domains/paywall/entities/index.ts +5 -0
  13. package/src/domains/paywall/entities/types.ts +48 -0
  14. package/src/domains/paywall/hooks/index.ts +6 -0
  15. package/src/{presentation → domains/paywall}/hooks/usePaywall.ts +1 -1
  16. package/src/domains/paywall/index.ts +13 -0
  17. package/src/index.ts +17 -21
  18. package/src/domain/entities/paywall/CreditsPackage.ts +0 -16
  19. package/src/domain/entities/paywall/PaywallTab.ts +0 -11
  20. package/src/domain/entities/paywall/SubscriptionPlan.ts +0 -27
  21. package/src/presentation/components/paywall/BestValueBadge.tsx +0 -59
  22. package/src/presentation/components/paywall/CreditsPackageCard.tsx +0 -129
  23. package/src/presentation/components/paywall/CreditsTabContent.tsx +0 -123
  24. package/src/presentation/components/paywall/PaywallFeatureItem.tsx +0 -65
  25. package/src/presentation/components/paywall/PaywallFeaturesList.tsx +0 -47
  26. package/src/presentation/components/paywall/PaywallHeader.tsx +0 -82
  27. package/src/presentation/components/paywall/PaywallLegalFooter.tsx +0 -145
  28. package/src/presentation/components/paywall/PaywallLegalFooterStyles.ts +0 -53
  29. package/src/presentation/components/paywall/PaywallLegalFooterTypes.ts +0 -19
  30. package/src/presentation/components/paywall/PaywallModal.tsx +0 -162
  31. package/src/presentation/components/paywall/PaywallTabBar.tsx +0 -96
  32. package/src/presentation/components/paywall/SubscriptionFooter.tsx +0 -116
  33. package/src/presentation/components/paywall/SubscriptionModal.tsx +0 -168
  34. package/src/presentation/components/paywall/SubscriptionModalHeader.tsx +0 -78
  35. package/src/presentation/components/paywall/SubscriptionPackageList.tsx +0 -171
  36. package/src/presentation/components/paywall/SubscriptionPlanCard.tsx +0 -213
  37. package/src/presentation/components/paywall/SubscriptionPlanCardStyles.ts +0 -61
  38. package/src/presentation/components/paywall/SubscriptionPlanCardTypes.ts +0 -15
  39. package/src/presentation/components/paywall/SubscriptionTabContent.tsx +0 -139
  40. package/src/presentation/components/paywall/accordion/AccordionPlanCard.tsx +0 -100
  41. package/src/presentation/components/paywall/accordion/AccordionPlanCardTypes.ts +0 -39
  42. package/src/presentation/components/paywall/accordion/PlanCardDetails.tsx +0 -107
  43. package/src/presentation/components/paywall/accordion/PlanCardHeader.tsx +0 -165
  44. package/src/presentation/components/paywall/accordion/index.ts +0 -12
  45. /package/src/{presentation → domains/paywall}/hooks/useSubscriptionModal.ts +0 -0
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Paywall Tab Bar
3
+ * Segmented control for hybrid mode
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, TouchableOpacity, StyleSheet, Animated } from "react-native";
8
+ import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
9
+ import type { PaywallTabType } from "../entities";
10
+
11
+ interface PaywallTabBarProps {
12
+ activeTab: PaywallTabType;
13
+ onTabChange: (tab: PaywallTabType) => void;
14
+ creditsLabel: string;
15
+ subscriptionLabel: string;
16
+ }
17
+
18
+ export const PaywallTabBar: React.FC<PaywallTabBarProps> = React.memo(
19
+ ({ activeTab, onTabChange, creditsLabel, subscriptionLabel }) => {
20
+ const tokens = useAppDesignTokens();
21
+ const animatedValue = React.useRef(
22
+ new Animated.Value(activeTab === "credits" ? 0 : 1)
23
+ ).current;
24
+
25
+ React.useEffect(() => {
26
+ Animated.spring(animatedValue, {
27
+ toValue: activeTab === "credits" ? 0 : 1,
28
+ useNativeDriver: false,
29
+ tension: 68,
30
+ friction: 12,
31
+ }).start();
32
+ }, [activeTab, animatedValue]);
33
+
34
+ const renderTab = (tab: PaywallTabType, label: string) => {
35
+ const isActive = activeTab === tab;
36
+
37
+ return (
38
+ <TouchableOpacity
39
+ key={tab}
40
+ style={styles.tab}
41
+ onPress={() => onTabChange(tab)}
42
+ activeOpacity={0.7}
43
+ >
44
+ <AtomicText
45
+ type="labelLarge"
46
+ style={[
47
+ styles.tabText,
48
+ { color: isActive ? tokens.colors.primary : tokens.colors.textSecondary },
49
+ ]}
50
+ >
51
+ {label}
52
+ </AtomicText>
53
+ </TouchableOpacity>
54
+ );
55
+ };
56
+
57
+ const indicatorLeft = animatedValue.interpolate({
58
+ inputRange: [0, 1],
59
+ outputRange: ["2%", "50%"],
60
+ });
61
+
62
+ return (
63
+ <View style={[styles.container, { backgroundColor: tokens.colors.surfaceSecondary }]}>
64
+ <Animated.View
65
+ style={[styles.indicator, { backgroundColor: tokens.colors.surface, left: indicatorLeft }]}
66
+ />
67
+ {renderTab("credits", creditsLabel)}
68
+ {renderTab("subscription", subscriptionLabel)}
69
+ </View>
70
+ );
71
+ }
72
+ );
73
+
74
+ PaywallTabBar.displayName = "PaywallTabBar";
75
+
76
+ const styles = StyleSheet.create({
77
+ container: {
78
+ flexDirection: "row",
79
+ borderRadius: 12,
80
+ padding: 4,
81
+ marginHorizontal: 24,
82
+ marginBottom: 16,
83
+ position: "relative",
84
+ height: 44,
85
+ },
86
+ indicator: {
87
+ position: "absolute",
88
+ top: 4,
89
+ bottom: 4,
90
+ width: "46%",
91
+ borderRadius: 8,
92
+ },
93
+ tab: {
94
+ flex: 1,
95
+ alignItems: "center",
96
+ justifyContent: "center",
97
+ zIndex: 1,
98
+ },
99
+ tabText: {
100
+ fontWeight: "600",
101
+ },
102
+ });
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Plan Card
3
+ * Subscription plan selection card
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
8
+ import { AtomicText, AtomicIcon, AtomicBadge, useAppDesignTokens } from "@umituz/react-native-design-system";
9
+ import type { PurchasesPackage } from "react-native-purchases";
10
+
11
+ interface PlanCardProps {
12
+ pkg: PurchasesPackage;
13
+ isSelected: boolean;
14
+ onSelect: () => void;
15
+ badge?: string;
16
+ creditAmount?: number;
17
+ creditsLabel?: string;
18
+ }
19
+
20
+ export const PlanCard: React.FC<PlanCardProps> = React.memo(
21
+ ({ pkg, isSelected, onSelect, badge, creditAmount, creditsLabel }) => {
22
+ const tokens = useAppDesignTokens();
23
+ const title = pkg.product.title;
24
+ const price = `${pkg.product.currencyCode} ${pkg.product.price.toFixed(2)}`;
25
+
26
+ return (
27
+ <TouchableOpacity onPress={onSelect} activeOpacity={0.7} style={styles.touchable}>
28
+ <View
29
+ style={[
30
+ styles.container,
31
+ {
32
+ backgroundColor: tokens.colors.surface,
33
+ borderColor: isSelected ? tokens.colors.primary : tokens.colors.border,
34
+ borderWidth: isSelected ? 2 : 1,
35
+ },
36
+ ]}
37
+ >
38
+ {badge && (
39
+ <View style={styles.badgeContainer}>
40
+ <AtomicBadge text={badge} variant="primary" size="sm" />
41
+ </View>
42
+ )}
43
+
44
+ <View style={styles.content}>
45
+ <View style={styles.leftSection}>
46
+ <View
47
+ style={[
48
+ styles.radio,
49
+ {
50
+ borderColor: isSelected ? tokens.colors.primary : tokens.colors.border,
51
+ backgroundColor: isSelected ? tokens.colors.primary : "transparent",
52
+ },
53
+ ]}
54
+ >
55
+ {isSelected && (
56
+ <AtomicIcon name="checkmark" customSize={12} customColor={tokens.colors.onPrimary} />
57
+ )}
58
+ </View>
59
+
60
+ <View style={styles.textSection}>
61
+ <AtomicText type="titleSmall" style={{ color: tokens.colors.textPrimary, fontWeight: "600" }}>
62
+ {title}
63
+ </AtomicText>
64
+ {creditAmount && creditsLabel && (
65
+ <AtomicText type="bodySmall" style={{ color: tokens.colors.textSecondary }}>
66
+ {creditAmount} {creditsLabel}
67
+ </AtomicText>
68
+ )}
69
+ </View>
70
+ </View>
71
+
72
+ <AtomicText
73
+ type="titleMedium"
74
+ style={{ color: isSelected ? tokens.colors.primary : tokens.colors.textPrimary, fontWeight: "700" }}
75
+ >
76
+ {price}
77
+ </AtomicText>
78
+ </View>
79
+ </View>
80
+ </TouchableOpacity>
81
+ );
82
+ }
83
+ );
84
+
85
+ PlanCard.displayName = "PlanCard";
86
+
87
+ const styles = StyleSheet.create({
88
+ touchable: {
89
+ marginBottom: 10,
90
+ marginHorizontal: 24,
91
+ },
92
+ container: {
93
+ borderRadius: 16,
94
+ padding: 16,
95
+ position: "relative",
96
+ },
97
+ badgeContainer: {
98
+ position: "absolute",
99
+ top: -10,
100
+ right: 16,
101
+ },
102
+ content: {
103
+ flexDirection: "row",
104
+ alignItems: "center",
105
+ justifyContent: "space-between",
106
+ },
107
+ leftSection: {
108
+ flexDirection: "row",
109
+ alignItems: "center",
110
+ flex: 1,
111
+ },
112
+ radio: {
113
+ width: 22,
114
+ height: 22,
115
+ borderRadius: 11,
116
+ borderWidth: 2,
117
+ alignItems: "center",
118
+ justifyContent: "center",
119
+ marginRight: 12,
120
+ },
121
+ textSection: {
122
+ flex: 1,
123
+ },
124
+ });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Paywall Components Index
3
+ */
4
+
5
+ export { PaywallModal } from "./PaywallModal";
6
+ export type { PaywallModalProps } from "./PaywallModal";
7
+
8
+ export { PaywallHeader } from "./PaywallHeader";
9
+ export { PaywallTabBar } from "./PaywallTabBar";
10
+ export { PaywallFooter } from "./PaywallFooter";
11
+ export { FeatureList } from "./FeatureList";
12
+ export { FeatureItem } from "./FeatureItem";
13
+ export { PlanCard } from "./PlanCard";
14
+ export { CreditCard } from "./CreditCard";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Paywall Entities Index
3
+ */
4
+
5
+ export * from "./types";
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Paywall Types
3
+ * All paywall-related type definitions
4
+ */
5
+
6
+ export type PaywallMode = "subscription" | "credits" | "hybrid";
7
+
8
+ export type PaywallTabType = "credits" | "subscription";
9
+
10
+ export interface PaywallTab {
11
+ id: PaywallTabType;
12
+ label: string;
13
+ }
14
+
15
+ export interface CreditsPackage {
16
+ id: string;
17
+ credits: number;
18
+ price: number;
19
+ currency: string;
20
+ bonus?: number;
21
+ badge?: string;
22
+ description?: string;
23
+ }
24
+
25
+ export interface SubscriptionFeature {
26
+ icon: string;
27
+ text: string;
28
+ }
29
+
30
+ export interface PaywallTranslations {
31
+ title: string;
32
+ subtitle?: string;
33
+ creditsTabLabel?: string;
34
+ subscriptionTabLabel?: string;
35
+ purchaseButtonText: string;
36
+ subscribeButtonText?: string;
37
+ restoreButtonText: string;
38
+ loadingText: string;
39
+ emptyText: string;
40
+ processingText: string;
41
+ privacyText?: string;
42
+ termsOfServiceText?: string;
43
+ }
44
+
45
+ export interface PaywallLegalUrls {
46
+ privacyUrl?: string;
47
+ termsUrl?: string;
48
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Paywall Hooks Index
3
+ */
4
+
5
+ export { usePaywall } from "./usePaywall";
6
+ export { useSubscriptionModal } from "./useSubscriptionModal";
@@ -1,6 +1,6 @@
1
1
  import { useState, useCallback } from "react";
2
2
  import type { PurchasesPackage } from "react-native-purchases";
3
- import type { PaywallTabType } from "../../domain/entities/paywall/PaywallTab";
3
+ import type { PaywallTabType } from "../entities";
4
4
 
5
5
  interface UsePaywallProps {
6
6
  initialTab?: PaywallTabType;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Paywall Domain
3
+ * Complete paywall solution for subscription and credits
4
+ */
5
+
6
+ // Entities
7
+ export * from "./entities";
8
+
9
+ // Components
10
+ export * from "./components";
11
+
12
+ // Hooks
13
+ export * from "./hooks";
package/src/index.ts CHANGED
@@ -60,32 +60,28 @@ export {
60
60
  } from "./presentation/hooks/useUserTierWithRepository";
61
61
 
62
62
  // =============================================================================
63
- // PRESENTATION LAYER - Paywall Components
63
+ // PAYWALL DOMAIN
64
64
  // =============================================================================
65
65
 
66
- export {
67
- SubscriptionModal,
68
- type SubscriptionModalProps,
69
- } from "./presentation/components/paywall/SubscriptionModal";
70
-
71
- export { SubscriptionModalHeader } from "./presentation/components/paywall/SubscriptionModalHeader";
72
-
73
- export { SubscriptionPlanCard } from "./presentation/components/paywall/SubscriptionPlanCard";
74
- export {
75
- AccordionPlanCard,
76
- PlanCardHeader,
77
- PlanCardDetails,
78
- type AccordionPlanCardProps,
79
- type PlanCardHeaderProps,
80
- type PlanCardDetailsProps,
81
- } from "./presentation/components/paywall/accordion";
82
- export { PaywallFeaturesList } from "./presentation/components/paywall/PaywallFeaturesList";
83
- export { PaywallFeatureItem } from "./presentation/components/paywall/PaywallFeatureItem";
84
- export { PaywallLegalFooter } from "./presentation/components/paywall/PaywallLegalFooter";
85
66
  export {
86
67
  PaywallModal,
87
68
  type PaywallModalProps,
88
- } from "./presentation/components/paywall/PaywallModal";
69
+ PaywallHeader,
70
+ PaywallTabBar,
71
+ PaywallFooter,
72
+ FeatureList,
73
+ FeatureItem,
74
+ PlanCard,
75
+ CreditCard,
76
+ usePaywall,
77
+ useSubscriptionModal,
78
+ type PaywallMode,
79
+ type PaywallTabType,
80
+ type CreditsPackage,
81
+ type SubscriptionFeature,
82
+ type PaywallTranslations,
83
+ type PaywallLegalUrls,
84
+ } from "./domains/paywall";
89
85
 
90
86
  // =============================================================================
91
87
  // PRESENTATION LAYER - Premium Details Components
@@ -1,16 +0,0 @@
1
- /**
2
- * Credits Package Entity
3
- * Represents a credit package for purchase
4
- */
5
-
6
- export interface CreditsPackage {
7
- id: string;
8
- credits: number;
9
- price: number;
10
- priceString?: string;
11
- currency: string;
12
- bonus?: number;
13
- popular?: boolean;
14
- badge?: string;
15
- description?: string;
16
- }
@@ -1,11 +0,0 @@
1
- /**
2
- * Paywall Tab Entity
3
- * Represents paywall tab types
4
- */
5
-
6
- export type PaywallTabType = "credits" | "subscription";
7
-
8
- export interface PaywallTab {
9
- id: PaywallTabType;
10
- label: string;
11
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Subscription Plan Entity
3
- * Represents a subscription plan for purchase
4
- */
5
-
6
- export interface SubscriptionPlan {
7
- /** Plan ID */
8
- id: string;
9
-
10
- /** Plan type */
11
- type: "monthly" | "yearly";
12
-
13
- /** Price */
14
- price: number;
15
-
16
- /** Currency code */
17
- currency: string;
18
-
19
- /** Whether this is the best value option */
20
- isBestValue?: boolean;
21
-
22
- /** Optional discount percentage */
23
- discountPercentage?: number;
24
-
25
- /** Optional features list */
26
- features?: string[];
27
- }
@@ -1,59 +0,0 @@
1
- import React, { useMemo } from "react";
2
- import { View, StyleSheet } from "react-native";
3
- import { AtomicText, useAppDesignTokens, useResponsive } from "@umituz/react-native-design-system";
4
- import { LinearGradient } from "expo-linear-gradient";
5
-
6
- interface BestValueBadgeProps {
7
- text: string;
8
- visible?: boolean;
9
- }
10
-
11
- export const BestValueBadge: React.FC<BestValueBadgeProps> = React.memo(
12
- ({ text, visible = true }) => {
13
- const tokens = useAppDesignTokens();
14
- const { spacingMultiplier, getFontSize } = useResponsive();
15
-
16
- const styles = useMemo(() => createStyles(spacingMultiplier), [spacingMultiplier]);
17
- const fontSize = getFontSize(10);
18
-
19
- if (!visible) return null;
20
-
21
- return (
22
- <View style={styles.badgeContainer}>
23
- <LinearGradient
24
- colors={[tokens.colors.secondary, tokens.colors.primary]}
25
- start={{ x: 0, y: 0 }}
26
- end={{ x: 1, y: 1 }}
27
- style={styles.badge}
28
- >
29
- <AtomicText
30
- type="labelSmall"
31
- style={{ color: tokens.colors.onPrimary, fontWeight: "800", textTransform: "uppercase", fontSize }}
32
- >
33
- {text}
34
- </AtomicText>
35
- </LinearGradient>
36
- </View>
37
- );
38
- }
39
- );
40
-
41
- BestValueBadge.displayName = "BestValueBadge";
42
-
43
- const createStyles = (spacingMult: number) =>
44
- StyleSheet.create({
45
- badgeContainer: {
46
- position: "absolute",
47
- top: -12 * spacingMult,
48
- right: 16 * spacingMult,
49
- zIndex: 1,
50
- alignSelf: "flex-end",
51
- },
52
- badge: {
53
- paddingHorizontal: 16 * spacingMult,
54
- paddingVertical: 6 * spacingMult,
55
- borderRadius: 16 * spacingMult,
56
- alignItems: "center",
57
- justifyContent: "center",
58
- },
59
- });
@@ -1,129 +0,0 @@
1
- /**
2
- * Credits Package Card Component
3
- * Single Responsibility: Display a single credits package option
4
- */
5
-
6
- import React from "react";
7
- import { View, StyleSheet, TouchableOpacity } from "react-native";
8
- import { AtomicText } from "@umituz/react-native-design-system";
9
- import { useAppDesignTokens } from "@umituz/react-native-design-system";
10
- import type { CreditsPackage } from "../../../domain/entities/paywall/CreditsPackage";
11
-
12
- interface CreditsPackageCardProps {
13
- package: CreditsPackage;
14
- isSelected: boolean;
15
- onSelect: () => void;
16
- }
17
-
18
- export const CreditsPackageCard: React.FC<CreditsPackageCardProps> =
19
- React.memo(({ package: pkg, isSelected, onSelect }) => {
20
- const tokens = useAppDesignTokens();
21
-
22
- const totalCredits = pkg.credits + (pkg.bonus || 0);
23
-
24
- return (
25
- <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
- onPress={onSelect}
39
- activeOpacity={0.8}
40
- >
41
- {pkg.badge && (
42
- <View
43
- style={[styles.badge, { backgroundColor: tokens.colors.warning }]}
44
- >
45
- <AtomicText
46
- type="labelSmall"
47
- style={{ color: tokens.colors.onPrimary, fontWeight: "700" }}
48
- >
49
- {pkg.badge}
50
- </AtomicText>
51
- </View>
52
- )}
53
- <View style={styles.content}>
54
- <View style={styles.leftSection}>
55
- <AtomicText
56
- type="headlineMedium"
57
- style={[styles.credits, { color: tokens.colors.textPrimary }]}
58
- >
59
- {totalCredits.toLocaleString()} Credits
60
- </AtomicText>
61
- {(pkg.bonus ?? 0) > 0 && (
62
- <AtomicText
63
- type="bodySmall"
64
- style={[styles.bonus, { color: tokens.colors.success }]}
65
- >
66
- +{pkg.bonus} bonus
67
- </AtomicText>
68
- )}
69
- {pkg.description && (
70
- <AtomicText
71
- type="bodySmall"
72
- style={{ color: tokens.colors.textSecondary }}
73
- >
74
- {pkg.description}
75
- </AtomicText>
76
- )}
77
- </View>
78
- <View style={styles.rightSection}>
79
- <AtomicText
80
- type="titleLarge"
81
- style={[styles.price, { color: tokens.colors.primary }]}
82
- >
83
- {pkg.currency} {pkg.price.toFixed(2)}
84
- </AtomicText>
85
- </View>
86
- </View>
87
- </TouchableOpacity>
88
- );
89
- });
90
-
91
- CreditsPackageCard.displayName = "CreditsPackageCard";
92
-
93
- const styles = StyleSheet.create({
94
- container: {
95
- borderRadius: 16,
96
- padding: 20,
97
- position: "relative",
98
- },
99
- badge: {
100
- position: "absolute",
101
- top: -10,
102
- right: 20,
103
- paddingHorizontal: 10,
104
- paddingVertical: 4,
105
- borderRadius: 8,
106
- },
107
- content: {
108
- flexDirection: "row",
109
- justifyContent: "space-between",
110
- alignItems: "center",
111
- },
112
- leftSection: {
113
- flex: 1,
114
- },
115
- credits: {
116
- fontWeight: "700",
117
- marginBottom: 4,
118
- },
119
- bonus: {
120
- fontWeight: "600",
121
- marginBottom: 4,
122
- },
123
- rightSection: {
124
- alignItems: "flex-end",
125
- },
126
- price: {
127
- fontWeight: "700",
128
- },
129
- });