@umituz/react-native-subscription 2.12.2 → 2.12.4

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 (47) hide show
  1. package/package.json +2 -2
  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/{presentation/components/paywall/PaywallHeroHeader.tsx → domains/paywall/components/PaywallHeader.tsx} +15 -44
  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 +15 -22
  18. package/src/presentation/components/details/PremiumDetailsCard.tsx +35 -11
  19. package/src/domain/entities/paywall/CreditsPackage.ts +0 -16
  20. package/src/domain/entities/paywall/PaywallMode.ts +0 -6
  21. package/src/domain/entities/paywall/PaywallTab.ts +0 -11
  22. package/src/domain/entities/paywall/SubscriptionPlan.ts +0 -27
  23. package/src/presentation/components/paywall/BestValueBadge.tsx +0 -59
  24. package/src/presentation/components/paywall/CreditsPackageCard.tsx +0 -161
  25. package/src/presentation/components/paywall/CreditsTabContent.tsx +0 -123
  26. package/src/presentation/components/paywall/PaywallFeatureItem.tsx +0 -79
  27. package/src/presentation/components/paywall/PaywallFeaturesList.tsx +0 -47
  28. package/src/presentation/components/paywall/PaywallHeader.tsx +0 -82
  29. package/src/presentation/components/paywall/PaywallLegalFooter.tsx +0 -145
  30. package/src/presentation/components/paywall/PaywallLegalFooterStyles.ts +0 -53
  31. package/src/presentation/components/paywall/PaywallLegalFooterTypes.ts +0 -19
  32. package/src/presentation/components/paywall/PaywallModal.tsx +0 -162
  33. package/src/presentation/components/paywall/PaywallTabBar.tsx +0 -120
  34. package/src/presentation/components/paywall/SubscriptionFooter.tsx +0 -116
  35. package/src/presentation/components/paywall/SubscriptionModal.tsx +0 -168
  36. package/src/presentation/components/paywall/SubscriptionModalHeader.tsx +0 -78
  37. package/src/presentation/components/paywall/SubscriptionPackageList.tsx +0 -171
  38. package/src/presentation/components/paywall/SubscriptionPlanCard.tsx +0 -213
  39. package/src/presentation/components/paywall/SubscriptionPlanCardStyles.ts +0 -61
  40. package/src/presentation/components/paywall/SubscriptionPlanCardTypes.ts +0 -15
  41. package/src/presentation/components/paywall/SubscriptionTabContent.tsx +0 -139
  42. package/src/presentation/components/paywall/accordion/AccordionPlanCard.tsx +0 -98
  43. package/src/presentation/components/paywall/accordion/AccordionPlanCardTypes.ts +0 -39
  44. package/src/presentation/components/paywall/accordion/PlanCardDetails.tsx +0 -107
  45. package/src/presentation/components/paywall/accordion/PlanCardHeader.tsx +0 -155
  46. package/src/presentation/components/paywall/accordion/index.ts +0 -12
  47. /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,35 +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,
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,
88
82
  type PaywallTranslations,
89
83
  type PaywallLegalUrls,
90
- } from "./presentation/components/paywall/PaywallModal";
91
- export type { PaywallMode } from "./domain/entities/paywall/PaywallMode";
84
+ } from "./domains/paywall";
92
85
 
93
86
  // =============================================================================
94
87
  // PRESENTATION LAYER - Premium Details Components
@@ -31,17 +31,30 @@ export const PremiumDetailsCard: React.FC<PremiumDetailsCardProps> = ({
31
31
 
32
32
  return (
33
33
  <View style={[styles.card, { backgroundColor: tokens.colors.surface }]}>
34
- <View style={styles.header}>
35
- <Text style={[styles.title, { color: tokens.colors.text }]}>
36
- {translations.title}
37
- </Text>
38
- <PremiumStatusBadge
39
- status={statusType}
40
- activeLabel={translations.statusActive}
41
- expiredLabel={translations.statusExpired}
42
- noneLabel={translations.statusFree}
43
- />
44
- </View>
34
+ {(isPremium || showCredits) && (
35
+ <View style={styles.header}>
36
+ <Text style={[styles.title, { color: tokens.colors.text }]}>
37
+ {translations.title}
38
+ </Text>
39
+ <PremiumStatusBadge
40
+ status={statusType}
41
+ activeLabel={translations.statusActive}
42
+ expiredLabel={translations.statusExpired}
43
+ noneLabel={translations.statusFree}
44
+ />
45
+ </View>
46
+ )}
47
+
48
+ {!isPremium && !showCredits && (
49
+ <View style={styles.freeUserHeader}>
50
+ <Text style={[styles.freeUserTitle, { color: tokens.colors.text }]}>
51
+ {translations.title}
52
+ </Text>
53
+ <Text style={[styles.freeUserDescription, { color: tokens.colors.textSecondary }]}>
54
+ {translations.upgradeButton ? `${translations.upgradeButton}` : "Unlock premium features"}
55
+ </Text>
56
+ </View>
57
+ )}
45
58
 
46
59
  {isPremium && (
47
60
  <View style={styles.detailsSection}>
@@ -141,6 +154,17 @@ const styles = StyleSheet.create({
141
154
  fontSize: 18,
142
155
  fontWeight: "600",
143
156
  },
157
+ freeUserHeader: {
158
+ gap: 4,
159
+ },
160
+ freeUserTitle: {
161
+ fontSize: 18,
162
+ fontWeight: "600",
163
+ },
164
+ freeUserDescription: {
165
+ fontSize: 14,
166
+ fontWeight: "400",
167
+ },
144
168
  detailsSection: {
145
169
  gap: 8,
146
170
  },
@@ -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,6 +0,0 @@
1
- /**
2
- * Paywall Mode Entity
3
- * Defines paywall display modes
4
- */
5
-
6
- export type PaywallMode = "subscription" | "credits" | "hybrid";
@@ -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
- });