@umituz/react-native-subscription 2.11.8 → 2.11.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.11.8",
3
+ "version": "2.11.9",
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
@@ -71,6 +71,14 @@ export {
71
71
  export { SubscriptionModalHeader } from "./presentation/components/paywall/SubscriptionModalHeader";
72
72
 
73
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";
74
82
  export { PaywallFeaturesList } from "./presentation/components/paywall/PaywallFeaturesList";
75
83
  export { PaywallFeatureItem } from "./presentation/components/paywall/PaywallFeatureItem";
76
84
  export { PaywallLegalFooter } from "./presentation/components/paywall/PaywallLegalFooter";
@@ -5,11 +5,10 @@
5
5
 
6
6
  import React from "react";
7
7
  import { View, StyleSheet, ScrollView } from "react-native";
8
- import { useAppDesignTokens, BaseModal } from "@umituz/react-native-design-system";
8
+ import { BaseModal } from "@umituz/react-native-design-system";
9
9
  import type { PurchasesPackage } from "react-native-purchases";
10
10
 
11
11
  import { SubscriptionModalHeader } from "./SubscriptionModalHeader";
12
- import { PaywallFeaturesList } from "./PaywallFeaturesList";
13
12
  import { SubscriptionPackageList } from "./SubscriptionPackageList";
14
13
  import { SubscriptionFooter } from "./SubscriptionFooter";
15
14
  import { useSubscriptionModal } from "../../hooks/useSubscriptionModal";
@@ -22,7 +21,6 @@ export interface SubscriptionModalProps {
22
21
  onRestore: () => Promise<boolean>;
23
22
  title: string;
24
23
  subtitle?: string;
25
- features?: Array<{ icon: string; text: string }>;
26
24
  isLoading?: boolean;
27
25
  purchaseButtonText: string;
28
26
  restoreButtonText: string;
@@ -49,7 +47,6 @@ export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo((p
49
47
  onRestore,
50
48
  title,
51
49
  subtitle,
52
- features,
53
50
  isLoading = false,
54
51
  purchaseButtonText,
55
52
  restoreButtonText,
@@ -65,8 +62,6 @@ export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo((p
65
62
  bestValueIdentifier,
66
63
  } = props;
67
64
 
68
- const tokens = useAppDesignTokens();
69
-
70
65
  const {
71
66
  selectedPkg,
72
67
  setSelectedPkg,
@@ -104,20 +99,6 @@ export const SubscriptionModal: React.FC<SubscriptionModalProps> = React.memo((p
104
99
  creditAmounts={creditAmounts}
105
100
  bestValueIdentifier={bestValueIdentifier}
106
101
  />
107
-
108
- {features && features.length > 0 && (
109
- <View
110
- style={[
111
- styles.featuresSection,
112
- {
113
- backgroundColor: tokens.colors.surfaceSecondary,
114
- borderColor: tokens.colors.border
115
- }
116
- ]}
117
- >
118
- <PaywallFeaturesList features={features} gap={12} />
119
- </View>
120
- )}
121
102
  </ScrollView>
122
103
 
123
104
  <SubscriptionFooter
@@ -155,10 +136,4 @@ const styles = StyleSheet.create({
155
136
  flexGrow: 1,
156
137
  paddingBottom: 32,
157
138
  },
158
- featuresSection: {
159
- borderRadius: 24,
160
- padding: 24,
161
- marginTop: 12,
162
- borderWidth: 1,
163
- },
164
139
  });
@@ -1,9 +1,9 @@
1
- import React from "react";
1
+ import React, { useState, useCallback } from "react";
2
2
  import { View, StyleSheet, ActivityIndicator } from "react-native";
3
3
  import { AtomicText } from "@umituz/react-native-design-system";
4
4
  import { useAppDesignTokens } from "@umituz/react-native-design-system";
5
5
  import type { PurchasesPackage } from "react-native-purchases";
6
- import { SubscriptionPlanCard } from "./SubscriptionPlanCard";
6
+ import { AccordionPlanCard } from "./accordion";
7
7
  import { isYearlyPackage, isMonthlyPackage, isWeeklyPackage } from "../../../utils/packagePeriodUtils";
8
8
 
9
9
  interface SubscriptionPackageListProps {
@@ -34,6 +34,12 @@ export const SubscriptionPackageList: React.FC<SubscriptionPackageListProps> = R
34
34
  const hasPackages = packages.length > 0;
35
35
  const showLoading = isLoading && !hasPackages;
36
36
 
37
+ const [expandedPackageId, setExpandedPackageId] = useState<string | null>(null);
38
+
39
+ const handleToggleExpand = useCallback((packageId: string) => {
40
+ setExpandedPackageId((prev) => (prev === packageId ? null : packageId));
41
+ }, []);
42
+
37
43
  if (showLoading) {
38
44
  return (
39
45
  <View style={styles.centerContent}>
@@ -115,13 +121,16 @@ export const SubscriptionPackageList: React.FC<SubscriptionPackageListProps> = R
115
121
  };
116
122
 
117
123
  const creditAmount = findCreditAmount();
124
+ const packageId = pkg.product.identifier;
118
125
 
119
126
  return (
120
- <SubscriptionPlanCard
121
- key={pkg.product.identifier}
127
+ <AccordionPlanCard
128
+ key={packageId}
122
129
  package={pkg}
123
- isSelected={selectedPkg?.product.identifier === pkg.product.identifier}
130
+ isSelected={selectedPkg?.product.identifier === packageId}
131
+ isExpanded={expandedPackageId === packageId}
124
132
  onSelect={() => onSelect(pkg)}
133
+ onToggleExpand={() => handleToggleExpand(packageId)}
125
134
  isBestValue={isBestValue}
126
135
  creditAmount={creditAmount}
127
136
  />
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Accordion Plan Card
3
+ * Expandable subscription plan card with credit display
4
+ */
5
+
6
+ import React, { useCallback } from "react";
7
+ import { View, StyleSheet, type StyleProp, type ViewStyle } from "react-native";
8
+ import { useAppDesignTokens } from "@umituz/react-native-design-system";
9
+ import { formatPrice } from "../../../../utils/priceUtils";
10
+ import { getPeriodLabel, isYearlyPackage } from "../../../../utils/packagePeriodUtils";
11
+ import { useLocalization } from "@umituz/react-native-localization";
12
+ import { PlanCardHeader } from "./PlanCardHeader";
13
+ import { PlanCardDetails } from "./PlanCardDetails";
14
+ import type { AccordionPlanCardProps } from "./AccordionPlanCardTypes";
15
+
16
+ export const AccordionPlanCard: React.FC<AccordionPlanCardProps> = React.memo(
17
+ ({
18
+ package: pkg,
19
+ isSelected,
20
+ isExpanded,
21
+ onSelect,
22
+ onToggleExpand,
23
+ isBestValue = false,
24
+ creditAmount,
25
+ }) => {
26
+ const tokens = useAppDesignTokens();
27
+ const { t } = useLocalization();
28
+
29
+ const period = pkg.product.subscriptionPeriod;
30
+ const isYearly = isYearlyPackage(pkg);
31
+ const periodLabel = getPeriodLabel(period);
32
+ const price = formatPrice(pkg.product.price, pkg.product.currencyCode);
33
+ const monthlyEquivalent = isYearly
34
+ ? formatPrice(pkg.product.price / 12, pkg.product.currencyCode)
35
+ : null;
36
+
37
+ const title = pkg.product.title || t(`paywall.period.${periodLabel}`);
38
+ const displayPrice = isYearly && monthlyEquivalent
39
+ ? `${monthlyEquivalent}/mo`
40
+ : price;
41
+
42
+ const handleHeaderPress = useCallback(() => {
43
+ onSelect();
44
+ if (!isExpanded) {
45
+ onToggleExpand();
46
+ }
47
+ }, [onSelect, onToggleExpand, isExpanded]);
48
+
49
+ const containerStyle: StyleProp<ViewStyle> = [
50
+ styles.container,
51
+ {
52
+ borderColor: isSelected
53
+ ? tokens.colors.primary
54
+ : tokens.colors.borderLight,
55
+ borderWidth: isSelected ? 2 : 1,
56
+ backgroundColor: tokens.colors.surface,
57
+ },
58
+ ];
59
+
60
+ return (
61
+ <View style={containerStyle}>
62
+ <PlanCardHeader
63
+ title={title}
64
+ price={displayPrice}
65
+ creditAmount={creditAmount}
66
+ isSelected={isSelected}
67
+ isExpanded={isExpanded}
68
+ isBestValue={isBestValue}
69
+ onToggle={handleHeaderPress}
70
+ />
71
+
72
+ {isExpanded && (
73
+ <PlanCardDetails
74
+ fullPrice={price}
75
+ monthlyEquivalent={monthlyEquivalent}
76
+ periodLabel={periodLabel}
77
+ isYearly={isYearly}
78
+ />
79
+ )}
80
+ </View>
81
+ );
82
+ }
83
+ );
84
+
85
+ AccordionPlanCard.displayName = "AccordionPlanCard";
86
+
87
+ const styles = StyleSheet.create({
88
+ container: {
89
+ borderRadius: 16,
90
+ overflow: "hidden",
91
+ marginBottom: 12,
92
+ },
93
+ });
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Accordion Plan Card Types
3
+ * Type definitions for accordion-style subscription cards
4
+ */
5
+
6
+ import type { PurchasesPackage } from "react-native-purchases";
7
+
8
+ export interface AccordionPlanCardProps {
9
+ package: PurchasesPackage;
10
+ isSelected: boolean;
11
+ isExpanded: boolean;
12
+ onSelect: () => void;
13
+ onToggleExpand: () => void;
14
+ isBestValue?: boolean;
15
+ creditAmount?: number;
16
+ }
17
+
18
+ export interface PlanCardHeaderProps {
19
+ title: string;
20
+ price: string;
21
+ creditAmount?: number;
22
+ isSelected: boolean;
23
+ isExpanded: boolean;
24
+ isBestValue?: boolean;
25
+ onToggle: () => void;
26
+ }
27
+
28
+ export interface PlanCardDetailsProps {
29
+ fullPrice: string;
30
+ monthlyEquivalent: string | null;
31
+ periodLabel: string;
32
+ isYearly: boolean;
33
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Plan Card Details
3
+ * Expanded state of accordion subscription card
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ useAppDesignTokens,
11
+ } from "@umituz/react-native-design-system";
12
+ import { useLocalization } from "@umituz/react-native-localization";
13
+ import type { PlanCardDetailsProps } from "./AccordionPlanCardTypes";
14
+
15
+ export const PlanCardDetails: React.FC<PlanCardDetailsProps> = ({
16
+ fullPrice,
17
+ monthlyEquivalent,
18
+ periodLabel,
19
+ isYearly,
20
+ }) => {
21
+ const tokens = useAppDesignTokens();
22
+ const { t } = useLocalization();
23
+
24
+ return (
25
+ <View
26
+ style={[
27
+ styles.container,
28
+ {
29
+ backgroundColor: tokens.colors.surfaceSecondary,
30
+ borderTopColor: tokens.colors.border,
31
+ },
32
+ ]}
33
+ >
34
+ <View style={styles.row}>
35
+ <AtomicText
36
+ type="bodyMedium"
37
+ style={{ color: tokens.colors.textSecondary }}
38
+ >
39
+ {t("paywall.billingPeriod") || "Billing Period"}
40
+ </AtomicText>
41
+ <AtomicText
42
+ type="bodyMedium"
43
+ style={{ color: tokens.colors.textPrimary, fontWeight: "600" }}
44
+ >
45
+ {t(`paywall.period.${periodLabel}`)}
46
+ </AtomicText>
47
+ </View>
48
+
49
+ {isYearly && (
50
+ <View style={styles.row}>
51
+ <AtomicText
52
+ type="bodyMedium"
53
+ style={{ color: tokens.colors.textSecondary }}
54
+ >
55
+ {t("paywall.totalPrice") || "Total Price"}
56
+ </AtomicText>
57
+ <AtomicText
58
+ type="bodyMedium"
59
+ style={{ color: tokens.colors.textPrimary, fontWeight: "600" }}
60
+ >
61
+ {fullPrice}
62
+ </AtomicText>
63
+ </View>
64
+ )}
65
+
66
+ {monthlyEquivalent && (
67
+ <View style={styles.row}>
68
+ <AtomicText
69
+ type="bodyMedium"
70
+ style={{ color: tokens.colors.textSecondary }}
71
+ >
72
+ {t("paywall.perMonth") || "Per Month"}
73
+ </AtomicText>
74
+ <AtomicText
75
+ type="bodyMedium"
76
+ style={{
77
+ color: tokens.colors.primary,
78
+ fontWeight: "700",
79
+ }}
80
+ >
81
+ {monthlyEquivalent}
82
+ </AtomicText>
83
+ </View>
84
+ )}
85
+ </View>
86
+ );
87
+ };
88
+
89
+ const styles = StyleSheet.create({
90
+ container: {
91
+ paddingHorizontal: 16,
92
+ paddingVertical: 12,
93
+ borderTopWidth: 1,
94
+ gap: 10,
95
+ },
96
+ row: {
97
+ flexDirection: "row",
98
+ justifyContent: "space-between",
99
+ alignItems: "center",
100
+ },
101
+ });
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Plan Card Header
3
+ * Collapsed state of accordion subscription card
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ withAlpha,
13
+ } from "@umituz/react-native-design-system";
14
+ import { BestValueBadge } from "../BestValueBadge";
15
+ import { useLocalization } from "@umituz/react-native-localization";
16
+ import type { PlanCardHeaderProps } from "./AccordionPlanCardTypes";
17
+
18
+ export const PlanCardHeader: React.FC<PlanCardHeaderProps> = ({
19
+ title,
20
+ price,
21
+ creditAmount,
22
+ isSelected,
23
+ isExpanded,
24
+ isBestValue,
25
+ onToggle,
26
+ }) => {
27
+ const tokens = useAppDesignTokens();
28
+ const { t } = useLocalization();
29
+
30
+ return (
31
+ <TouchableOpacity
32
+ onPress={onToggle}
33
+ activeOpacity={0.7}
34
+ style={styles.container}
35
+ >
36
+ <BestValueBadge text={t("paywall.bestValue")} visible={isBestValue} />
37
+
38
+ <View style={styles.content}>
39
+ <View style={styles.leftSection}>
40
+ <View
41
+ style={[
42
+ styles.radio,
43
+ {
44
+ borderColor: isSelected
45
+ ? tokens.colors.primary
46
+ : tokens.colors.border,
47
+ },
48
+ ]}
49
+ >
50
+ {isSelected && (
51
+ <View
52
+ style={[
53
+ styles.radioInner,
54
+ { backgroundColor: tokens.colors.primary },
55
+ ]}
56
+ />
57
+ )}
58
+ </View>
59
+
60
+ <View style={styles.textContainer}>
61
+ <AtomicText
62
+ type="titleSmall"
63
+ style={{ color: tokens.colors.textPrimary, fontWeight: "600" }}
64
+ >
65
+ {title}
66
+ </AtomicText>
67
+ {creditAmount && (
68
+ <View
69
+ style={[
70
+ styles.creditBadge,
71
+ {
72
+ backgroundColor: withAlpha(tokens.colors.primary, 0.15),
73
+ borderColor: withAlpha(tokens.colors.primary, 0.3),
74
+ },
75
+ ]}
76
+ >
77
+ <AtomicText
78
+ type="labelSmall"
79
+ style={{
80
+ color: tokens.colors.primary,
81
+ fontWeight: "700",
82
+ fontSize: 11,
83
+ }}
84
+ >
85
+ {creditAmount} {t("paywall.credits") || "Credits"}
86
+ </AtomicText>
87
+ </View>
88
+ )}
89
+ </View>
90
+ </View>
91
+
92
+ <View style={styles.rightSection}>
93
+ <AtomicText
94
+ type="titleMedium"
95
+ style={{
96
+ color: tokens.colors.textPrimary,
97
+ fontWeight: "700",
98
+ marginRight: 8,
99
+ }}
100
+ >
101
+ {price}
102
+ </AtomicText>
103
+ <AtomicIcon
104
+ name={isExpanded ? "ChevronUp" : "ChevronDown"}
105
+ size={20}
106
+ color={tokens.colors.textSecondary}
107
+ />
108
+ </View>
109
+ </View>
110
+ </TouchableOpacity>
111
+ );
112
+ };
113
+
114
+ const styles = StyleSheet.create({
115
+ container: {
116
+ width: "100%",
117
+ },
118
+ content: {
119
+ flexDirection: "row",
120
+ alignItems: "center",
121
+ justifyContent: "space-between",
122
+ paddingVertical: 16,
123
+ paddingHorizontal: 16,
124
+ },
125
+ leftSection: {
126
+ flexDirection: "row",
127
+ alignItems: "center",
128
+ flex: 1,
129
+ },
130
+ radio: {
131
+ width: 22,
132
+ height: 22,
133
+ borderRadius: 11,
134
+ borderWidth: 2,
135
+ alignItems: "center",
136
+ justifyContent: "center",
137
+ marginRight: 12,
138
+ },
139
+ radioInner: {
140
+ width: 12,
141
+ height: 12,
142
+ borderRadius: 6,
143
+ },
144
+ textContainer: {
145
+ flex: 1,
146
+ gap: 6,
147
+ },
148
+ creditBadge: {
149
+ paddingHorizontal: 10,
150
+ paddingVertical: 4,
151
+ borderRadius: 12,
152
+ borderWidth: 1,
153
+ alignSelf: "flex-start",
154
+ },
155
+ rightSection: {
156
+ flexDirection: "row",
157
+ alignItems: "center",
158
+ },
159
+ });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Accordion Components Barrel Export
3
+ */
4
+
5
+ export { AccordionPlanCard } from "./AccordionPlanCard";
6
+ export { PlanCardHeader } from "./PlanCardHeader";
7
+ export { PlanCardDetails } from "./PlanCardDetails";
8
+ export type {
9
+ AccordionPlanCardProps,
10
+ PlanCardHeaderProps,
11
+ PlanCardDetailsProps,
12
+ } from "./AccordionPlanCardTypes";