@umituz/react-native-subscription 2.1.0 → 2.1.1

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.1.0",
3
+ "version": "2.1.1",
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
@@ -124,6 +124,18 @@ export {
124
124
  type SubscriptionSectionConfig,
125
125
  } from "./presentation/components/sections/SubscriptionSection";
126
126
 
127
+ // =============================================================================
128
+ // PRESENTATION LAYER - Subscription Detail Screen
129
+ // =============================================================================
130
+
131
+ export {
132
+ SubscriptionDetailScreen,
133
+ type SubscriptionDetailScreenProps,
134
+ type SubscriptionDetailConfig,
135
+ type SubscriptionDetailTranslations,
136
+ } from "./presentation/screens/SubscriptionDetailScreen";
137
+
138
+
127
139
  // =============================================================================
128
140
  // UTILS - Date & Price
129
141
  // =============================================================================
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import React from "react";
8
- import { View } from "react-native";
8
+ import { View, TouchableOpacity } from "react-native";
9
9
  import type { StyleProp, ViewStyle } from "react-native";
10
10
  import {
11
11
  PremiumDetailsCard,
@@ -35,6 +35,8 @@ export interface SubscriptionSectionConfig {
35
35
  onManageSubscription?: () => void;
36
36
  /** Handler for upgrade button */
37
37
  onUpgrade?: () => void;
38
+ /** Handler for when section is tapped (navigate to detail screen) */
39
+ onPress?: () => void;
38
40
  }
39
41
 
40
42
  export interface SubscriptionSectionProps {
@@ -46,20 +48,32 @@ export const SubscriptionSection: React.FC<SubscriptionSectionProps> = ({
46
48
  config,
47
49
  containerStyle,
48
50
  }) => {
49
- return (
50
- <View style={containerStyle}>
51
- <PremiumDetailsCard
52
- statusType={config.statusType}
53
- isPremium={config.isPremium}
54
- expirationDate={config.expirationDate}
55
- purchaseDate={config.purchaseDate}
56
- isLifetime={config.isLifetime}
57
- daysRemaining={config.daysRemaining}
58
- credits={config.credits}
59
- translations={config.translations}
60
- onManageSubscription={config.onManageSubscription}
61
- onUpgrade={config.onUpgrade}
62
- />
63
- </View>
51
+ const content = (
52
+ <PremiumDetailsCard
53
+ statusType={config.statusType}
54
+ isPremium={config.isPremium}
55
+ expirationDate={config.expirationDate}
56
+ purchaseDate={config.purchaseDate}
57
+ isLifetime={config.isLifetime}
58
+ daysRemaining={config.daysRemaining}
59
+ credits={config.credits}
60
+ translations={config.translations}
61
+ onManageSubscription={config.onManageSubscription}
62
+ onUpgrade={config.onUpgrade}
63
+ />
64
64
  );
65
+
66
+ if (config.onPress) {
67
+ return (
68
+ <TouchableOpacity
69
+ style={containerStyle}
70
+ onPress={config.onPress}
71
+ activeOpacity={0.7}
72
+ >
73
+ {content}
74
+ </TouchableOpacity>
75
+ );
76
+ }
77
+
78
+ return <View style={containerStyle}>{content}</View>;
65
79
  };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Subscription Detail Screen
3
+ * Composition of subscription components
4
+ * No business logic - pure presentation
5
+ */
6
+
7
+ import React from "react";
8
+ import { ScrollView, StyleSheet } from "react-native";
9
+ import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
10
+ import { SubscriptionHeader } from "./components/SubscriptionHeader";
11
+ import { CreditsList } from "./components/CreditsList";
12
+ import { SubscriptionActions } from "./components/SubscriptionActions";
13
+ import type { SubscriptionStatusType } from "../components/details/PremiumStatusBadge";
14
+ import type { CreditInfo } from "../components/details/PremiumDetailsCard";
15
+
16
+ export interface SubscriptionDetailTranslations {
17
+ title: string;
18
+ statusLabel?: string;
19
+ statusActive?: string;
20
+ statusExpired?: string;
21
+ statusFree?: string;
22
+ expiresLabel?: string;
23
+ purchasedLabel?: string;
24
+ lifetimeLabel?: string;
25
+ creditsTitle?: string;
26
+ remainingLabel?: string;
27
+ usageTitle?: string;
28
+ manageButton?: string;
29
+ upgradeButton?: string;
30
+ creditsResetInfo?: string;
31
+ }
32
+
33
+ export interface SubscriptionDetailConfig {
34
+ statusType: SubscriptionStatusType;
35
+ isPremium: boolean;
36
+ expirationDate?: string | null;
37
+ purchaseDate?: string | null;
38
+ isLifetime?: boolean;
39
+ daysRemaining?: number | null;
40
+ credits?: CreditInfo[];
41
+ translations: SubscriptionDetailTranslations;
42
+ onManageSubscription?: () => void;
43
+ onUpgrade?: () => void;
44
+ }
45
+
46
+ export interface SubscriptionDetailScreenProps {
47
+ config: SubscriptionDetailConfig;
48
+ }
49
+
50
+ export const SubscriptionDetailScreen: React.FC<
51
+ SubscriptionDetailScreenProps
52
+ > = ({ config }) => {
53
+ const tokens = useAppDesignTokens();
54
+ const showCredits = config.credits && config.credits.length > 0;
55
+
56
+ return (
57
+ <ScrollView
58
+ style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
59
+ contentContainerStyle={styles.content}
60
+ >
61
+ <SubscriptionHeader
62
+ statusType={config.statusType}
63
+ isPremium={config.isPremium}
64
+ isLifetime={config.isLifetime}
65
+ expirationDate={config.expirationDate}
66
+ purchaseDate={config.purchaseDate}
67
+ daysRemaining={config.daysRemaining}
68
+ translations={config.translations}
69
+ />
70
+
71
+ {showCredits && (
72
+ <CreditsList
73
+ credits={config.credits!}
74
+ title={
75
+ config.translations.usageTitle || config.translations.creditsTitle
76
+ }
77
+ description={config.translations.creditsResetInfo}
78
+ remainingLabel={config.translations.remainingLabel}
79
+ />
80
+ )}
81
+
82
+ <SubscriptionActions
83
+ isPremium={config.isPremium}
84
+ manageButtonLabel={config.translations.manageButton}
85
+ upgradeButtonLabel={config.translations.upgradeButton}
86
+ onManage={config.onManageSubscription}
87
+ onUpgrade={config.onUpgrade}
88
+ />
89
+ </ScrollView>
90
+ );
91
+ };
92
+
93
+ const styles = StyleSheet.create({
94
+ container: {
95
+ flex: 1,
96
+ },
97
+ content: {
98
+ padding: 16,
99
+ gap: 16,
100
+ },
101
+ });
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Credit Item Component
3
+ * Displays individual credit usage with progress bar
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, Text, StyleSheet } from "react-native";
8
+ import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
9
+
10
+ interface CreditItemProps {
11
+ label: string;
12
+ current: number;
13
+ total: number;
14
+ remainingLabel?: string;
15
+ }
16
+
17
+ export const CreditItem: React.FC<CreditItemProps> = ({
18
+ label,
19
+ current,
20
+ total,
21
+ remainingLabel = "remaining",
22
+ }) => {
23
+ const tokens = useAppDesignTokens();
24
+ const percentage = total > 0 ? (current / total) * 100 : 0;
25
+ const isLow = percentage <= 20;
26
+ const isMedium = percentage > 20 && percentage <= 50;
27
+
28
+ const getColor = () => {
29
+ if (isLow) return tokens.colors.error;
30
+ if (isMedium) return tokens.colors.warning;
31
+ return tokens.colors.success;
32
+ };
33
+
34
+ return (
35
+ <View style={styles.container}>
36
+ <View style={styles.header}>
37
+ <Text style={[styles.label, { color: tokens.colors.textPrimary }]}>
38
+ {label}
39
+ </Text>
40
+ <View style={[styles.badge, { backgroundColor: tokens.colors.surfaceSecondary }]}>
41
+ <Text style={[styles.count, { color: getColor() }]}>
42
+ {current} / {total}
43
+ </Text>
44
+ </View>
45
+ </View>
46
+ <View
47
+ style={[
48
+ styles.progressBar,
49
+ { backgroundColor: tokens.colors.surfaceSecondary },
50
+ ]}
51
+ >
52
+ <View
53
+ style={[
54
+ styles.progressFill,
55
+ {
56
+ width: `${percentage}%`,
57
+ backgroundColor: getColor(),
58
+ },
59
+ ]}
60
+ />
61
+ </View>
62
+ <Text style={[styles.remaining, { color: tokens.colors.textSecondary }]}>
63
+ {current} {remainingLabel}
64
+ </Text>
65
+ </View>
66
+ );
67
+ };
68
+
69
+ const styles = StyleSheet.create({
70
+ container: {
71
+ gap: 8,
72
+ },
73
+ header: {
74
+ flexDirection: "row",
75
+ justifyContent: "space-between",
76
+ alignItems: "center",
77
+ },
78
+ label: {
79
+ fontSize: 15,
80
+ fontWeight: "500",
81
+ },
82
+ badge: {
83
+ paddingHorizontal: 12,
84
+ paddingVertical: 4,
85
+ borderRadius: 12,
86
+ },
87
+ count: {
88
+ fontSize: 13,
89
+ fontWeight: "600",
90
+ },
91
+ progressBar: {
92
+ height: 8,
93
+ borderRadius: 4,
94
+ overflow: "hidden",
95
+ },
96
+ progressFill: {
97
+ height: "100%",
98
+ borderRadius: 4,
99
+ },
100
+ remaining: {
101
+ fontSize: 12,
102
+ },
103
+ });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Credits List Component
3
+ * Displays list of credit usages
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, Text, StyleSheet } from "react-native";
8
+ import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
9
+ import { CreditItem } from "./CreditItem";
10
+ import type { CreditInfo } from "../../components/details/PremiumDetailsCard";
11
+
12
+ interface CreditsListProps {
13
+ credits: CreditInfo[];
14
+ title?: string;
15
+ description?: string;
16
+ remainingLabel?: string;
17
+ }
18
+
19
+ export const CreditsList: React.FC<CreditsListProps> = ({
20
+ credits,
21
+ title,
22
+ description,
23
+ remainingLabel,
24
+ }) => {
25
+ const tokens = useAppDesignTokens();
26
+
27
+ return (
28
+ <View style={[styles.container, { backgroundColor: tokens.colors.surface }]}>
29
+ {title && (
30
+ <Text style={[styles.title, { color: tokens.colors.textPrimary }]}>
31
+ {title}
32
+ </Text>
33
+ )}
34
+ {description && (
35
+ <Text style={[styles.description, { color: tokens.colors.textSecondary }]}>
36
+ {description}
37
+ </Text>
38
+ )}
39
+ <View style={styles.list}>
40
+ {credits.map((credit) => (
41
+ <CreditItem
42
+ key={credit.id}
43
+ label={credit.label}
44
+ current={credit.current}
45
+ total={credit.total}
46
+ remainingLabel={remainingLabel}
47
+ />
48
+ ))}
49
+ </View>
50
+ </View>
51
+ );
52
+ };
53
+
54
+ const styles = StyleSheet.create({
55
+ container: {
56
+ borderRadius: 16,
57
+ padding: 20,
58
+ gap: 16,
59
+ },
60
+ title: {
61
+ fontSize: 18,
62
+ fontWeight: "700",
63
+ },
64
+ description: {
65
+ fontSize: 14,
66
+ marginTop: -8,
67
+ },
68
+ list: {
69
+ gap: 16,
70
+ },
71
+ });
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Subscription Actions Component
3
+ * Displays action buttons for subscription management
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, Text, StyleSheet, TouchableOpacity } from "react-native";
8
+ import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
9
+
10
+ interface SubscriptionActionsProps {
11
+ isPremium: boolean;
12
+ manageButtonLabel?: string;
13
+ upgradeButtonLabel?: string;
14
+ onManage?: () => void;
15
+ onUpgrade?: () => void;
16
+ }
17
+
18
+ export const SubscriptionActions: React.FC<SubscriptionActionsProps> = ({
19
+ isPremium,
20
+ manageButtonLabel,
21
+ upgradeButtonLabel,
22
+ onManage,
23
+ onUpgrade,
24
+ }) => {
25
+ const tokens = useAppDesignTokens();
26
+
27
+ return (
28
+ <View style={styles.container}>
29
+ {isPremium && onManage && manageButtonLabel && (
30
+ <TouchableOpacity
31
+ style={[
32
+ styles.secondaryButton,
33
+ { backgroundColor: tokens.colors.surfaceSecondary },
34
+ ]}
35
+ onPress={onManage}
36
+ >
37
+ <Text
38
+ style={[
39
+ styles.secondaryButtonText,
40
+ { color: tokens.colors.textPrimary },
41
+ ]}
42
+ >
43
+ {manageButtonLabel}
44
+ </Text>
45
+ </TouchableOpacity>
46
+ )}
47
+ {!isPremium && onUpgrade && upgradeButtonLabel && (
48
+ <TouchableOpacity
49
+ style={[styles.primaryButton, { backgroundColor: tokens.colors.primary }]}
50
+ onPress={onUpgrade}
51
+ >
52
+ <Text
53
+ style={[
54
+ styles.primaryButtonText,
55
+ { color: tokens.colors.onPrimary },
56
+ ]}
57
+ >
58
+ {upgradeButtonLabel}
59
+ </Text>
60
+ </TouchableOpacity>
61
+ )}
62
+ </View>
63
+ );
64
+ };
65
+
66
+ const styles = StyleSheet.create({
67
+ container: {
68
+ gap: 12,
69
+ paddingBottom: 32,
70
+ },
71
+ primaryButton: {
72
+ paddingVertical: 16,
73
+ borderRadius: 12,
74
+ alignItems: "center",
75
+ },
76
+ primaryButtonText: {
77
+ fontSize: 16,
78
+ fontWeight: "700",
79
+ },
80
+ secondaryButton: {
81
+ paddingVertical: 16,
82
+ borderRadius: 12,
83
+ alignItems: "center",
84
+ },
85
+ secondaryButtonText: {
86
+ fontSize: 16,
87
+ fontWeight: "600",
88
+ },
89
+ });
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Subscription Header Component
3
+ * Displays status badge and subscription details
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, Text, StyleSheet } from "react-native";
8
+ import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
9
+ import {
10
+ PremiumStatusBadge,
11
+ type SubscriptionStatusType,
12
+ } from "../../components/details/PremiumStatusBadge";
13
+
14
+ interface SubscriptionHeaderTranslations {
15
+ title: string;
16
+ statusLabel?: string;
17
+ statusActive?: string;
18
+ statusExpired?: string;
19
+ statusFree?: string;
20
+ expiresLabel?: string;
21
+ purchasedLabel?: string;
22
+ lifetimeLabel?: string;
23
+ }
24
+
25
+ interface SubscriptionHeaderProps {
26
+ statusType: SubscriptionStatusType;
27
+ isPremium: boolean;
28
+ isLifetime?: boolean;
29
+ expirationDate?: string | null;
30
+ purchaseDate?: string | null;
31
+ daysRemaining?: number | null;
32
+ translations: SubscriptionHeaderTranslations;
33
+ }
34
+
35
+ export const SubscriptionHeader: React.FC<SubscriptionHeaderProps> = ({
36
+ statusType,
37
+ isPremium,
38
+ isLifetime,
39
+ expirationDate,
40
+ purchaseDate,
41
+ daysRemaining,
42
+ translations,
43
+ }) => {
44
+ const tokens = useAppDesignTokens();
45
+ const showExpiring =
46
+ daysRemaining !== null &&
47
+ daysRemaining !== undefined &&
48
+ daysRemaining <= 7;
49
+
50
+ return (
51
+ <View style={[styles.container, { backgroundColor: tokens.colors.surface }]}>
52
+ <View style={styles.header}>
53
+ <Text style={[styles.title, { color: tokens.colors.textPrimary }]}>
54
+ {translations.title}
55
+ </Text>
56
+ <PremiumStatusBadge
57
+ status={statusType}
58
+ activeLabel={translations.statusActive}
59
+ expiredLabel={translations.statusExpired}
60
+ noneLabel={translations.statusFree}
61
+ />
62
+ </View>
63
+
64
+ {isPremium && (
65
+ <View style={styles.details}>
66
+ {isLifetime ? (
67
+ <DetailRow
68
+ label={translations.statusLabel || "Subscription"}
69
+ value={translations.lifetimeLabel || "Lifetime Access"}
70
+ tokens={tokens}
71
+ />
72
+ ) : (
73
+ <>
74
+ {expirationDate && (
75
+ <DetailRow
76
+ label={translations.expiresLabel || "Expires"}
77
+ value={expirationDate}
78
+ highlight={showExpiring}
79
+ tokens={tokens}
80
+ />
81
+ )}
82
+ {purchaseDate && (
83
+ <DetailRow
84
+ label={translations.purchasedLabel || "Purchased"}
85
+ value={purchaseDate}
86
+ tokens={tokens}
87
+ />
88
+ )}
89
+ </>
90
+ )}
91
+ </View>
92
+ )}
93
+ </View>
94
+ );
95
+ };
96
+
97
+ interface DetailRowProps {
98
+ label: string;
99
+ value: string;
100
+ highlight?: boolean;
101
+ tokens: ReturnType<typeof useAppDesignTokens>;
102
+ }
103
+
104
+ const DetailRow: React.FC<DetailRowProps> = ({
105
+ label,
106
+ value,
107
+ highlight,
108
+ tokens,
109
+ }) => (
110
+ <View style={styles.row}>
111
+ <Text style={[styles.label, { color: tokens.colors.textSecondary }]}>
112
+ {label}
113
+ </Text>
114
+ <Text
115
+ style={[
116
+ styles.value,
117
+ { color: highlight ? tokens.colors.warning : tokens.colors.textPrimary },
118
+ ]}
119
+ >
120
+ {value}
121
+ </Text>
122
+ </View>
123
+ );
124
+
125
+ const styles = StyleSheet.create({
126
+ container: {
127
+ borderRadius: 16,
128
+ padding: 20,
129
+ gap: 16,
130
+ },
131
+ header: {
132
+ flexDirection: "row",
133
+ justifyContent: "space-between",
134
+ alignItems: "center",
135
+ },
136
+ title: {
137
+ fontSize: 24,
138
+ fontWeight: "700",
139
+ },
140
+ details: {
141
+ gap: 12,
142
+ paddingTop: 12,
143
+ },
144
+ row: {
145
+ flexDirection: "row",
146
+ justifyContent: "space-between",
147
+ alignItems: "center",
148
+ },
149
+ label: {
150
+ fontSize: 15,
151
+ },
152
+ value: {
153
+ fontSize: 15,
154
+ fontWeight: "600",
155
+ },
156
+ });