@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 +1 -1
- package/src/index.ts +12 -0
- package/src/presentation/components/sections/SubscriptionSection.tsx +30 -16
- package/src/presentation/screens/SubscriptionDetailScreen.tsx +101 -0
- package/src/presentation/screens/components/CreditItem.tsx +103 -0
- package/src/presentation/screens/components/CreditsList.tsx +71 -0
- package/src/presentation/screens/components/SubscriptionActions.tsx +89 -0
- package/src/presentation/screens/components/SubscriptionHeader.tsx +156 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.1.
|
|
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
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
+
});
|