@umituz/react-native-subscription 2.14.17 → 2.14.18
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 +9 -1
- package/src/presentation/components/details/PremiumDetailsCard.tsx +2 -2
- package/src/presentation/components/details/PremiumStatusBadge.tsx +31 -27
- package/src/presentation/screens/SubscriptionDetailScreen.tsx +78 -111
- package/src/presentation/screens/components/CreditItem.tsx +81 -86
- package/src/presentation/screens/components/CreditsList.tsx +62 -60
- package/src/presentation/screens/components/DevTestSection.tsx +70 -66
- package/src/presentation/screens/components/SubscriptionActions.tsx +36 -41
- package/src/presentation/screens/components/SubscriptionHeader.tsx +138 -144
- package/src/presentation/types/SubscriptionDetailTypes.ts +113 -0
- package/src/presentation/types/SubscriptionSettingsTypes.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.14.
|
|
3
|
+
"version": "2.14.18",
|
|
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
|
@@ -172,9 +172,17 @@ export {
|
|
|
172
172
|
type SubscriptionDetailScreenProps,
|
|
173
173
|
type SubscriptionDetailConfig,
|
|
174
174
|
type SubscriptionDetailTranslations,
|
|
175
|
+
type DevTestActions,
|
|
176
|
+
type DevToolsConfig,
|
|
175
177
|
} from "./presentation/screens/SubscriptionDetailScreen";
|
|
176
178
|
|
|
177
|
-
export
|
|
179
|
+
export type {
|
|
180
|
+
SubscriptionHeaderProps,
|
|
181
|
+
CreditsListProps,
|
|
182
|
+
CreditItemProps,
|
|
183
|
+
SubscriptionActionsProps,
|
|
184
|
+
DevTestSectionProps,
|
|
185
|
+
} from "./presentation/types/SubscriptionDetailTypes";
|
|
178
186
|
|
|
179
187
|
|
|
180
188
|
// =============================================================================
|
|
@@ -63,10 +63,10 @@ export const PremiumDetailsCard: React.FC<PremiumDetailsCardProps> = ({
|
|
|
63
63
|
|
|
64
64
|
{isPremium && (
|
|
65
65
|
<View style={styles.detailsSection}>
|
|
66
|
-
{isLifetime ? (
|
|
66
|
+
{isLifetime && translations.lifetimeLabel ? (
|
|
67
67
|
<DetailRow
|
|
68
68
|
label={translations.statusLabel}
|
|
69
|
-
value={translations.lifetimeLabel
|
|
69
|
+
value={translations.lifetimeLabel}
|
|
70
70
|
/>
|
|
71
71
|
) : (
|
|
72
72
|
<>
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* Displays subscription status as a colored badge
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React from "react";
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
7
|
import { View, StyleSheet } from "react-native";
|
|
8
8
|
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
9
|
-
import { SubscriptionStatusType } from
|
|
9
|
+
import type { SubscriptionStatusType } from "../../../domain/entities/SubscriptionStatus";
|
|
10
|
+
|
|
10
11
|
export type { SubscriptionStatusType };
|
|
11
12
|
|
|
12
13
|
export interface PremiumStatusBadgeProps {
|
|
@@ -17,9 +18,6 @@ export interface PremiumStatusBadgeProps {
|
|
|
17
18
|
canceledLabel: string;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
/**
|
|
21
|
-
* Badge component showing subscription status
|
|
22
|
-
*/
|
|
23
21
|
export const PremiumStatusBadge: React.FC<PremiumStatusBadgeProps> = ({
|
|
24
22
|
status,
|
|
25
23
|
activeLabel,
|
|
@@ -36,32 +34,38 @@ export const PremiumStatusBadge: React.FC<PremiumStatusBadgeProps> = ({
|
|
|
36
34
|
canceled: canceledLabel,
|
|
37
35
|
};
|
|
38
36
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
const backgroundColor = useMemo(() => {
|
|
38
|
+
const colors: Record<SubscriptionStatusType, string> = {
|
|
39
|
+
active: tokens.colors.success,
|
|
40
|
+
expired: tokens.colors.error,
|
|
41
|
+
none: tokens.colors.textTertiary,
|
|
42
|
+
canceled: tokens.colors.warning,
|
|
43
|
+
};
|
|
44
|
+
return colors[status];
|
|
45
|
+
}, [status, tokens.colors]);
|
|
45
46
|
|
|
46
|
-
const
|
|
47
|
-
|
|
47
|
+
const styles = useMemo(
|
|
48
|
+
() =>
|
|
49
|
+
StyleSheet.create({
|
|
50
|
+
badge: {
|
|
51
|
+
paddingHorizontal: tokens.spacing.sm,
|
|
52
|
+
paddingVertical: tokens.spacing.xs,
|
|
53
|
+
borderRadius: tokens.borderRadius.xs,
|
|
54
|
+
backgroundColor,
|
|
55
|
+
},
|
|
56
|
+
badgeText: {
|
|
57
|
+
fontWeight: "600",
|
|
58
|
+
color: tokens.colors.onPrimary,
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
61
|
+
[tokens, backgroundColor]
|
|
62
|
+
);
|
|
48
63
|
|
|
49
64
|
return (
|
|
50
|
-
<View style={
|
|
51
|
-
<AtomicText type="labelSmall" style={
|
|
52
|
-
{
|
|
65
|
+
<View style={styles.badge}>
|
|
66
|
+
<AtomicText type="labelSmall" style={styles.badgeText}>
|
|
67
|
+
{labels[status]}
|
|
53
68
|
</AtomicText>
|
|
54
69
|
</View>
|
|
55
70
|
);
|
|
56
71
|
};
|
|
57
|
-
|
|
58
|
-
const styles = StyleSheet.create({
|
|
59
|
-
badge: {
|
|
60
|
-
paddingHorizontal: 8,
|
|
61
|
-
paddingVertical: 4,
|
|
62
|
-
borderRadius: 4,
|
|
63
|
-
},
|
|
64
|
-
badgeText: {
|
|
65
|
-
fontWeight: "600",
|
|
66
|
-
},
|
|
67
|
-
});
|
|
@@ -10,123 +10,90 @@ import { useAppDesignTokens, ScreenLayout } from "@umituz/react-native-design-sy
|
|
|
10
10
|
import { SubscriptionHeader } from "./components/SubscriptionHeader";
|
|
11
11
|
import { CreditsList } from "./components/CreditsList";
|
|
12
12
|
import { SubscriptionActions } from "./components/SubscriptionActions";
|
|
13
|
-
import { DevTestSection
|
|
14
|
-
import type {
|
|
15
|
-
import type { CreditInfo } from "../components/details/PremiumDetailsCard";
|
|
13
|
+
import { DevTestSection } from "./components/DevTestSection";
|
|
14
|
+
import type { SubscriptionDetailScreenProps } from "../types/SubscriptionDetailTypes";
|
|
16
15
|
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
expiresLabel: string;
|
|
25
|
-
purchasedLabel: string;
|
|
26
|
-
lifetimeLabel: string;
|
|
27
|
-
creditsTitle: string;
|
|
28
|
-
remainingLabel: string;
|
|
29
|
-
usageTitle?: string;
|
|
30
|
-
manageButton: string;
|
|
31
|
-
upgradeButton: string;
|
|
32
|
-
creditsResetInfo?: string;
|
|
33
|
-
}
|
|
16
|
+
export type {
|
|
17
|
+
SubscriptionDetailTranslations,
|
|
18
|
+
SubscriptionDetailConfig,
|
|
19
|
+
SubscriptionDetailScreenProps,
|
|
20
|
+
DevTestActions,
|
|
21
|
+
DevToolsConfig,
|
|
22
|
+
} from "../types/SubscriptionDetailTypes";
|
|
34
23
|
|
|
35
|
-
export
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
daysRemaining?: number | null;
|
|
42
|
-
willRenew?: boolean;
|
|
43
|
-
credits?: CreditInfo[];
|
|
44
|
-
translations: SubscriptionDetailTranslations;
|
|
45
|
-
onManageSubscription?: () => void;
|
|
46
|
-
onUpgrade?: () => void;
|
|
47
|
-
devTools?: {
|
|
48
|
-
actions: DevTestActions;
|
|
49
|
-
title?: string;
|
|
50
|
-
};
|
|
51
|
-
}
|
|
24
|
+
export const SubscriptionDetailScreen: React.FC<SubscriptionDetailScreenProps> = ({
|
|
25
|
+
config,
|
|
26
|
+
}) => {
|
|
27
|
+
const tokens = useAppDesignTokens();
|
|
28
|
+
const showCredits = config.credits && config.credits.length > 0;
|
|
29
|
+
const showUpgradeButton = !config.isPremium && config.onUpgrade;
|
|
52
30
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
31
|
+
const styles = useMemo(
|
|
32
|
+
() =>
|
|
33
|
+
StyleSheet.create({
|
|
34
|
+
content: {
|
|
35
|
+
flexGrow: 1,
|
|
36
|
+
padding: tokens.spacing.lg,
|
|
37
|
+
gap: tokens.spacing.lg,
|
|
38
|
+
},
|
|
39
|
+
cardsContainer: {
|
|
40
|
+
gap: tokens.spacing.lg,
|
|
41
|
+
},
|
|
42
|
+
spacer: {
|
|
43
|
+
flex: 1,
|
|
44
|
+
minHeight: tokens.spacing.xl,
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
[tokens]
|
|
48
|
+
);
|
|
56
49
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
return (
|
|
51
|
+
<ScreenLayout
|
|
52
|
+
scrollable={true}
|
|
53
|
+
edges={["bottom"]}
|
|
54
|
+
backgroundColor={tokens.colors.backgroundPrimary}
|
|
55
|
+
contentContainerStyle={styles.content}
|
|
56
|
+
footer={
|
|
57
|
+
config.devTools ? (
|
|
58
|
+
<DevTestSection
|
|
59
|
+
actions={config.devTools.actions}
|
|
60
|
+
title={config.devTools.title}
|
|
61
|
+
/>
|
|
62
|
+
) : undefined
|
|
63
|
+
}
|
|
64
|
+
>
|
|
65
|
+
<View style={styles.cardsContainer}>
|
|
66
|
+
<SubscriptionHeader
|
|
67
|
+
statusType={config.statusType}
|
|
68
|
+
isPremium={config.isPremium}
|
|
69
|
+
isLifetime={config.isLifetime}
|
|
70
|
+
expirationDate={config.expirationDate}
|
|
71
|
+
purchaseDate={config.purchaseDate}
|
|
72
|
+
daysRemaining={config.daysRemaining}
|
|
73
|
+
translations={config.translations}
|
|
74
|
+
/>
|
|
63
75
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
padding: tokens.spacing.lg,
|
|
70
|
-
gap: tokens.spacing.lg,
|
|
71
|
-
},
|
|
72
|
-
cardsContainer: {
|
|
73
|
-
gap: tokens.spacing.lg,
|
|
74
|
-
},
|
|
75
|
-
spacer: {
|
|
76
|
-
flex: 1,
|
|
77
|
-
minHeight: tokens.spacing.xl,
|
|
78
|
-
},
|
|
79
|
-
}),
|
|
80
|
-
[tokens]
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<ScreenLayout
|
|
85
|
-
scrollable={true}
|
|
86
|
-
edges={['bottom']}
|
|
87
|
-
backgroundColor={tokens.colors.backgroundPrimary}
|
|
88
|
-
contentContainerStyle={styles.content}
|
|
89
|
-
footer={
|
|
90
|
-
config.devTools ? (
|
|
91
|
-
<DevTestSection
|
|
92
|
-
actions={config.devTools.actions}
|
|
93
|
-
title={config.devTools.title}
|
|
94
|
-
/>
|
|
95
|
-
) : undefined
|
|
76
|
+
{showCredits && (
|
|
77
|
+
<CreditsList
|
|
78
|
+
credits={config.credits!}
|
|
79
|
+
title={
|
|
80
|
+
config.translations.usageTitle || config.translations.creditsTitle
|
|
96
81
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
isLifetime={config.isLifetime}
|
|
103
|
-
expirationDate={config.expirationDate}
|
|
104
|
-
purchaseDate={config.purchaseDate}
|
|
105
|
-
daysRemaining={config.daysRemaining}
|
|
106
|
-
translations={config.translations}
|
|
107
|
-
/>
|
|
108
|
-
|
|
109
|
-
{showCredits && (
|
|
110
|
-
<CreditsList
|
|
111
|
-
credits={config.credits!}
|
|
112
|
-
title={
|
|
113
|
-
config.translations.usageTitle || config.translations.creditsTitle
|
|
114
|
-
}
|
|
115
|
-
description={config.translations.creditsResetInfo}
|
|
116
|
-
remainingLabel={config.translations.remainingLabel}
|
|
117
|
-
/>
|
|
118
|
-
)}
|
|
119
|
-
</View>
|
|
82
|
+
description={config.translations.creditsResetInfo}
|
|
83
|
+
remainingLabel={config.translations.remainingLabel}
|
|
84
|
+
/>
|
|
85
|
+
)}
|
|
86
|
+
</View>
|
|
120
87
|
|
|
121
|
-
|
|
88
|
+
<View style={styles.spacer} />
|
|
122
89
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
90
|
+
{showUpgradeButton && (
|
|
91
|
+
<SubscriptionActions
|
|
92
|
+
isPremium={config.isPremium}
|
|
93
|
+
upgradeButtonLabel={config.translations.upgradeButton}
|
|
94
|
+
onUpgrade={config.onUpgrade}
|
|
95
|
+
/>
|
|
96
|
+
)}
|
|
97
|
+
</ScreenLayout>
|
|
98
|
+
);
|
|
132
99
|
};
|
|
@@ -3,98 +3,93 @@
|
|
|
3
3
|
* Displays individual credit usage with progress bar
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React from "react";
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
7
|
import { View, StyleSheet } from "react-native";
|
|
8
8
|
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
9
|
-
|
|
10
|
-
interface CreditItemProps {
|
|
11
|
-
label: string;
|
|
12
|
-
current: number;
|
|
13
|
-
total: number;
|
|
14
|
-
remainingLabel?: string;
|
|
15
|
-
}
|
|
9
|
+
import type { CreditItemProps } from "../../types/SubscriptionDetailTypes";
|
|
16
10
|
|
|
17
11
|
export const CreditItem: React.FC<CreditItemProps> = ({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
label,
|
|
13
|
+
current,
|
|
14
|
+
total,
|
|
15
|
+
remainingLabel,
|
|
22
16
|
}) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
const tokens = useAppDesignTokens();
|
|
18
|
+
const percentage = total > 0 ? (current / total) * 100 : 0;
|
|
19
|
+
const isLow = percentage <= 20;
|
|
20
|
+
const isMedium = percentage > 20 && percentage <= 50;
|
|
21
|
+
|
|
22
|
+
const progressColor = useMemo(() => {
|
|
23
|
+
if (isLow) return tokens.colors.error;
|
|
24
|
+
if (isMedium) return tokens.colors.warning;
|
|
25
|
+
return tokens.colors.success;
|
|
26
|
+
}, [isLow, isMedium, tokens.colors]);
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
const styles = useMemo(
|
|
29
|
+
() =>
|
|
30
|
+
StyleSheet.create({
|
|
31
|
+
container: {
|
|
32
|
+
gap: tokens.spacing.sm,
|
|
33
|
+
},
|
|
34
|
+
header: {
|
|
35
|
+
flexDirection: "row",
|
|
36
|
+
justifyContent: "space-between",
|
|
37
|
+
alignItems: "center",
|
|
38
|
+
},
|
|
39
|
+
label: {
|
|
40
|
+
fontWeight: "500",
|
|
41
|
+
},
|
|
42
|
+
badge: {
|
|
43
|
+
paddingHorizontal: tokens.spacing.md,
|
|
44
|
+
paddingVertical: tokens.spacing.xs,
|
|
45
|
+
borderRadius: tokens.borderRadius.md,
|
|
46
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
47
|
+
},
|
|
48
|
+
count: {
|
|
49
|
+
fontWeight: "600",
|
|
50
|
+
},
|
|
51
|
+
progressBar: {
|
|
52
|
+
height: 8,
|
|
53
|
+
borderRadius: tokens.borderRadius.xs,
|
|
54
|
+
overflow: "hidden",
|
|
55
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
56
|
+
},
|
|
57
|
+
progressFill: {
|
|
58
|
+
height: "100%",
|
|
59
|
+
borderRadius: tokens.borderRadius.xs,
|
|
60
|
+
width: `${percentage}%`,
|
|
61
|
+
backgroundColor: progressColor,
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
[tokens, percentage, progressColor]
|
|
65
|
+
);
|
|
33
66
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
]}
|
|
51
|
-
>
|
|
52
|
-
<View
|
|
53
|
-
style={[
|
|
54
|
-
styles.progressFill,
|
|
55
|
-
{
|
|
56
|
-
width: `${percentage}%`,
|
|
57
|
-
backgroundColor: getColor(),
|
|
58
|
-
},
|
|
59
|
-
]}
|
|
60
|
-
/>
|
|
61
|
-
</View>
|
|
62
|
-
<AtomicText type="bodySmall" style={[styles.remaining, { color: tokens.colors.textSecondary }]}>
|
|
63
|
-
{current} {remainingLabel}
|
|
64
|
-
</AtomicText>
|
|
67
|
+
return (
|
|
68
|
+
<View style={styles.container}>
|
|
69
|
+
<View style={styles.header}>
|
|
70
|
+
<AtomicText
|
|
71
|
+
type="bodyMedium"
|
|
72
|
+
style={[styles.label, { color: tokens.colors.textPrimary }]}
|
|
73
|
+
>
|
|
74
|
+
{label}
|
|
75
|
+
</AtomicText>
|
|
76
|
+
<View style={styles.badge}>
|
|
77
|
+
<AtomicText
|
|
78
|
+
type="labelSmall"
|
|
79
|
+
style={[styles.count, { color: progressColor }]}
|
|
80
|
+
>
|
|
81
|
+
{current} / {total}
|
|
82
|
+
</AtomicText>
|
|
65
83
|
</View>
|
|
66
|
-
|
|
84
|
+
</View>
|
|
85
|
+
<View style={styles.progressBar}>
|
|
86
|
+
<View style={styles.progressFill} />
|
|
87
|
+
</View>
|
|
88
|
+
{remainingLabel && (
|
|
89
|
+
<AtomicText type="bodySmall" style={{ color: tokens.colors.textSecondary }}>
|
|
90
|
+
{current} {remainingLabel}
|
|
91
|
+
</AtomicText>
|
|
92
|
+
)}
|
|
93
|
+
</View>
|
|
94
|
+
);
|
|
67
95
|
};
|
|
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
|
-
fontWeight: "500",
|
|
80
|
-
},
|
|
81
|
-
badge: {
|
|
82
|
-
paddingHorizontal: 12,
|
|
83
|
-
paddingVertical: 4,
|
|
84
|
-
borderRadius: 12,
|
|
85
|
-
},
|
|
86
|
-
count: {
|
|
87
|
-
fontWeight: "600",
|
|
88
|
-
},
|
|
89
|
-
progressBar: {
|
|
90
|
-
height: 8,
|
|
91
|
-
borderRadius: 4,
|
|
92
|
-
overflow: "hidden",
|
|
93
|
-
},
|
|
94
|
-
progressFill: {
|
|
95
|
-
height: "100%",
|
|
96
|
-
borderRadius: 4,
|
|
97
|
-
},
|
|
98
|
-
remaining: {
|
|
99
|
-
},
|
|
100
|
-
});
|
|
@@ -3,69 +3,71 @@
|
|
|
3
3
|
* Displays list of credit usages
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React from "react";
|
|
7
|
-
import { View,
|
|
8
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
|
+
import { View, StyleSheet } from "react-native";
|
|
8
|
+
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
9
9
|
import { CreditItem } from "./CreditItem";
|
|
10
|
-
import type {
|
|
11
|
-
|
|
12
|
-
interface CreditsListProps {
|
|
13
|
-
credits: CreditInfo[];
|
|
14
|
-
title?: string;
|
|
15
|
-
description?: string;
|
|
16
|
-
remainingLabel?: string;
|
|
17
|
-
}
|
|
10
|
+
import type { CreditsListProps } from "../../types/SubscriptionDetailTypes";
|
|
18
11
|
|
|
19
12
|
export const CreditsList: React.FC<CreditsListProps> = ({
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
13
|
+
credits,
|
|
14
|
+
title,
|
|
15
|
+
description,
|
|
16
|
+
remainingLabel,
|
|
24
17
|
}) => {
|
|
25
|
-
|
|
18
|
+
const tokens = useAppDesignTokens();
|
|
26
19
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
))}
|
|
49
|
-
</View>
|
|
50
|
-
</View>
|
|
51
|
-
);
|
|
52
|
-
};
|
|
20
|
+
const styles = useMemo(
|
|
21
|
+
() =>
|
|
22
|
+
StyleSheet.create({
|
|
23
|
+
container: {
|
|
24
|
+
borderRadius: tokens.borderRadius.lg,
|
|
25
|
+
padding: tokens.spacing.lg,
|
|
26
|
+
gap: tokens.spacing.lg,
|
|
27
|
+
backgroundColor: tokens.colors.surface,
|
|
28
|
+
},
|
|
29
|
+
title: {
|
|
30
|
+
fontWeight: "700",
|
|
31
|
+
},
|
|
32
|
+
description: {
|
|
33
|
+
marginTop: -tokens.spacing.sm,
|
|
34
|
+
},
|
|
35
|
+
list: {
|
|
36
|
+
gap: tokens.spacing.lg,
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
[tokens]
|
|
40
|
+
);
|
|
53
41
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
42
|
+
return (
|
|
43
|
+
<View style={styles.container}>
|
|
44
|
+
{title && (
|
|
45
|
+
<AtomicText
|
|
46
|
+
type="titleMedium"
|
|
47
|
+
style={[styles.title, { color: tokens.colors.textPrimary }]}
|
|
48
|
+
>
|
|
49
|
+
{title}
|
|
50
|
+
</AtomicText>
|
|
51
|
+
)}
|
|
52
|
+
{description && (
|
|
53
|
+
<AtomicText
|
|
54
|
+
type="bodySmall"
|
|
55
|
+
style={[styles.description, { color: tokens.colors.textSecondary }]}
|
|
56
|
+
>
|
|
57
|
+
{description}
|
|
58
|
+
</AtomicText>
|
|
59
|
+
)}
|
|
60
|
+
<View style={styles.list}>
|
|
61
|
+
{credits.map((credit) => (
|
|
62
|
+
<CreditItem
|
|
63
|
+
key={credit.id}
|
|
64
|
+
label={credit.label}
|
|
65
|
+
current={credit.current}
|
|
66
|
+
total={credit.total}
|
|
67
|
+
remainingLabel={remainingLabel}
|
|
68
|
+
/>
|
|
69
|
+
))}
|
|
70
|
+
</View>
|
|
71
|
+
</View>
|
|
72
|
+
);
|
|
73
|
+
};
|
|
@@ -4,118 +4,122 @@
|
|
|
4
4
|
* Only visible in __DEV__ mode
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import React from "react";
|
|
7
|
+
import React, { useMemo } from "react";
|
|
8
8
|
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
9
9
|
import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
10
|
+
import type { DevTestSectionProps } from "../../types/SubscriptionDetailTypes";
|
|
10
11
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
export type { DevTestActions, DevTestSectionProps } from "../../types/SubscriptionDetailTypes";
|
|
13
|
+
|
|
14
|
+
/** Dev test button translations */
|
|
15
|
+
export interface DevTestTranslations {
|
|
16
|
+
title: string;
|
|
17
|
+
testRenewal: string;
|
|
18
|
+
checkCredits: string;
|
|
19
|
+
testDuplicate: string;
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
export interface DevTestSectionProps {
|
|
18
|
-
|
|
19
|
-
title?: string;
|
|
22
|
+
export interface DevTestSectionWithTranslationsProps extends DevTestSectionProps {
|
|
23
|
+
translations?: DevTestTranslations;
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
export const DevTestSection: React.FC<
|
|
26
|
+
export const DevTestSection: React.FC<DevTestSectionWithTranslationsProps> = ({
|
|
23
27
|
actions,
|
|
24
|
-
title
|
|
28
|
+
title,
|
|
29
|
+
translations,
|
|
25
30
|
}) => {
|
|
26
31
|
const tokens = useAppDesignTokens();
|
|
27
32
|
|
|
33
|
+
const styles = useMemo(
|
|
34
|
+
() =>
|
|
35
|
+
StyleSheet.create({
|
|
36
|
+
container: {
|
|
37
|
+
padding: tokens.spacing.lg,
|
|
38
|
+
gap: tokens.spacing.md,
|
|
39
|
+
borderTopWidth: 1,
|
|
40
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
41
|
+
borderTopColor: tokens.colors.border,
|
|
42
|
+
},
|
|
43
|
+
title: {
|
|
44
|
+
fontWeight: "600",
|
|
45
|
+
marginBottom: tokens.spacing.xs,
|
|
46
|
+
},
|
|
47
|
+
button: {
|
|
48
|
+
paddingVertical: tokens.spacing.md,
|
|
49
|
+
paddingHorizontal: tokens.spacing.lg,
|
|
50
|
+
borderRadius: tokens.borderRadius.md,
|
|
51
|
+
alignItems: "center",
|
|
52
|
+
},
|
|
53
|
+
primaryButton: {
|
|
54
|
+
backgroundColor: tokens.colors.primary,
|
|
55
|
+
},
|
|
56
|
+
secondaryButton: {
|
|
57
|
+
backgroundColor: tokens.colors.surfaceSecondary,
|
|
58
|
+
borderWidth: 1,
|
|
59
|
+
borderColor: tokens.colors.border,
|
|
60
|
+
},
|
|
61
|
+
buttonText: {
|
|
62
|
+
fontWeight: "500",
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
[tokens]
|
|
66
|
+
);
|
|
67
|
+
|
|
28
68
|
if (!__DEV__) {
|
|
29
69
|
return null;
|
|
30
70
|
}
|
|
31
71
|
|
|
72
|
+
const displayTitle = title || translations?.title;
|
|
73
|
+
const renewalText = translations?.testRenewal || "Test Auto-Renewal";
|
|
74
|
+
const creditsText = translations?.checkCredits || "Check Credits";
|
|
75
|
+
const duplicateText = translations?.testDuplicate || "Test Duplicate Protection";
|
|
76
|
+
|
|
32
77
|
return (
|
|
33
|
-
<View
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<AtomicText
|
|
43
|
-
type="titleMedium"
|
|
44
|
-
style={[styles.title, { color: tokens.colors.textPrimary }]}
|
|
45
|
-
>
|
|
46
|
-
{title}
|
|
47
|
-
</AtomicText>
|
|
78
|
+
<View style={styles.container}>
|
|
79
|
+
{displayTitle && (
|
|
80
|
+
<AtomicText
|
|
81
|
+
type="titleMedium"
|
|
82
|
+
style={[styles.title, { color: tokens.colors.textPrimary }]}
|
|
83
|
+
>
|
|
84
|
+
{displayTitle}
|
|
85
|
+
</AtomicText>
|
|
86
|
+
)}
|
|
48
87
|
|
|
49
88
|
<TouchableOpacity
|
|
50
|
-
style={[styles.button,
|
|
89
|
+
style={[styles.button, styles.primaryButton]}
|
|
51
90
|
onPress={actions.onTestRenewal}
|
|
52
91
|
>
|
|
53
92
|
<AtomicText
|
|
54
93
|
type="bodyMedium"
|
|
55
94
|
style={[styles.buttonText, { color: tokens.colors.onPrimary }]}
|
|
56
95
|
>
|
|
57
|
-
|
|
96
|
+
{renewalText}
|
|
58
97
|
</AtomicText>
|
|
59
98
|
</TouchableOpacity>
|
|
60
99
|
|
|
61
100
|
<TouchableOpacity
|
|
62
|
-
style={[
|
|
63
|
-
styles.button,
|
|
64
|
-
{
|
|
65
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
66
|
-
borderWidth: 1,
|
|
67
|
-
borderColor: tokens.colors.border,
|
|
68
|
-
},
|
|
69
|
-
]}
|
|
101
|
+
style={[styles.button, styles.secondaryButton]}
|
|
70
102
|
onPress={actions.onCheckCredits}
|
|
71
103
|
>
|
|
72
104
|
<AtomicText
|
|
73
105
|
type="bodyMedium"
|
|
74
106
|
style={[styles.buttonText, { color: tokens.colors.textPrimary }]}
|
|
75
107
|
>
|
|
76
|
-
|
|
108
|
+
{creditsText}
|
|
77
109
|
</AtomicText>
|
|
78
110
|
</TouchableOpacity>
|
|
79
111
|
|
|
80
112
|
<TouchableOpacity
|
|
81
|
-
style={[
|
|
82
|
-
styles.button,
|
|
83
|
-
{
|
|
84
|
-
backgroundColor: tokens.colors.surfaceSecondary,
|
|
85
|
-
borderWidth: 1,
|
|
86
|
-
borderColor: tokens.colors.border,
|
|
87
|
-
},
|
|
88
|
-
]}
|
|
113
|
+
style={[styles.button, styles.secondaryButton]}
|
|
89
114
|
onPress={actions.onTestDuplicate}
|
|
90
115
|
>
|
|
91
116
|
<AtomicText
|
|
92
117
|
type="bodyMedium"
|
|
93
118
|
style={[styles.buttonText, { color: tokens.colors.textPrimary }]}
|
|
94
119
|
>
|
|
95
|
-
|
|
120
|
+
{duplicateText}
|
|
96
121
|
</AtomicText>
|
|
97
122
|
</TouchableOpacity>
|
|
98
123
|
</View>
|
|
99
124
|
);
|
|
100
125
|
};
|
|
101
|
-
|
|
102
|
-
const styles = StyleSheet.create({
|
|
103
|
-
container: {
|
|
104
|
-
padding: 16,
|
|
105
|
-
gap: 12,
|
|
106
|
-
borderTopWidth: 1,
|
|
107
|
-
},
|
|
108
|
-
title: {
|
|
109
|
-
fontWeight: "600",
|
|
110
|
-
marginBottom: 4,
|
|
111
|
-
},
|
|
112
|
-
button: {
|
|
113
|
-
paddingVertical: 12,
|
|
114
|
-
paddingHorizontal: 16,
|
|
115
|
-
borderRadius: 8,
|
|
116
|
-
alignItems: "center",
|
|
117
|
-
},
|
|
118
|
-
buttonText: {
|
|
119
|
-
fontWeight: "500",
|
|
120
|
-
},
|
|
121
|
-
});
|
|
@@ -6,51 +6,46 @@
|
|
|
6
6
|
import React, { useMemo } from "react";
|
|
7
7
|
import { View, StyleSheet, TouchableOpacity } from "react-native";
|
|
8
8
|
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
9
|
-
|
|
10
|
-
interface SubscriptionActionsProps {
|
|
11
|
-
isPremium: boolean;
|
|
12
|
-
upgradeButtonLabel?: string;
|
|
13
|
-
onUpgrade?: () => void;
|
|
14
|
-
}
|
|
9
|
+
import type { SubscriptionActionsProps } from "../../types/SubscriptionDetailTypes";
|
|
15
10
|
|
|
16
11
|
export const SubscriptionActions: React.FC<SubscriptionActionsProps> = ({
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
isPremium,
|
|
13
|
+
upgradeButtonLabel,
|
|
14
|
+
onUpgrade,
|
|
20
15
|
}) => {
|
|
21
|
-
|
|
16
|
+
const tokens = useAppDesignTokens();
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
18
|
+
const styles = useMemo(
|
|
19
|
+
() =>
|
|
20
|
+
StyleSheet.create({
|
|
21
|
+
container: {
|
|
22
|
+
paddingBottom: tokens.spacing.xl,
|
|
23
|
+
},
|
|
24
|
+
primaryButton: {
|
|
25
|
+
paddingVertical: tokens.spacing.md,
|
|
26
|
+
borderRadius: tokens.borderRadius.lg,
|
|
27
|
+
alignItems: "center",
|
|
28
|
+
backgroundColor: tokens.colors.primary,
|
|
29
|
+
},
|
|
30
|
+
buttonText: {
|
|
31
|
+
color: tokens.colors.onPrimary,
|
|
32
|
+
fontWeight: "700",
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
[tokens]
|
|
36
|
+
);
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
if (isPremium || !onUpgrade || !upgradeButtonLabel) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
42
|
+
return (
|
|
43
|
+
<View style={styles.container}>
|
|
44
|
+
<TouchableOpacity style={styles.primaryButton} onPress={onUpgrade}>
|
|
45
|
+
<AtomicText type="titleMedium" style={styles.buttonText}>
|
|
46
|
+
{upgradeButtonLabel}
|
|
47
|
+
</AtomicText>
|
|
48
|
+
</TouchableOpacity>
|
|
49
|
+
</View>
|
|
50
|
+
);
|
|
56
51
|
};
|
|
@@ -3,163 +3,157 @@
|
|
|
3
3
|
* Displays status badge and subscription details
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React from "react";
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
7
|
import { View, StyleSheet } from "react-native";
|
|
8
8
|
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
9
|
-
import {
|
|
10
|
-
|
|
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
|
-
statusCanceled: string;
|
|
21
|
-
expiresLabel: string;
|
|
22
|
-
purchasedLabel: string;
|
|
23
|
-
lifetimeLabel: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface SubscriptionHeaderProps {
|
|
27
|
-
statusType: SubscriptionStatusType;
|
|
28
|
-
isPremium: boolean;
|
|
29
|
-
isLifetime?: boolean;
|
|
30
|
-
expirationDate?: string | null;
|
|
31
|
-
purchaseDate?: string | null;
|
|
32
|
-
daysRemaining?: number | null;
|
|
33
|
-
translations: SubscriptionHeaderTranslations;
|
|
34
|
-
}
|
|
9
|
+
import { PremiumStatusBadge } from "../../components/details/PremiumStatusBadge";
|
|
10
|
+
import type { SubscriptionHeaderProps } from "../../types/SubscriptionDetailTypes";
|
|
35
11
|
|
|
36
12
|
export const SubscriptionHeader: React.FC<SubscriptionHeaderProps> = ({
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
13
|
+
statusType,
|
|
14
|
+
isPremium,
|
|
15
|
+
isLifetime,
|
|
16
|
+
expirationDate,
|
|
17
|
+
purchaseDate,
|
|
18
|
+
daysRemaining,
|
|
19
|
+
translations,
|
|
44
20
|
}) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
daysRemaining !== undefined &&
|
|
49
|
-
daysRemaining <= 7;
|
|
21
|
+
const tokens = useAppDesignTokens();
|
|
22
|
+
const showExpiring =
|
|
23
|
+
daysRemaining !== null && daysRemaining !== undefined && daysRemaining <= 7;
|
|
50
24
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
25
|
+
const styles = useMemo(
|
|
26
|
+
() =>
|
|
27
|
+
StyleSheet.create({
|
|
28
|
+
container: {
|
|
29
|
+
borderRadius: tokens.borderRadius.lg,
|
|
30
|
+
padding: tokens.spacing.lg,
|
|
31
|
+
gap: tokens.spacing.lg,
|
|
32
|
+
backgroundColor: tokens.colors.surface,
|
|
33
|
+
},
|
|
34
|
+
header: {
|
|
35
|
+
flexDirection: "row",
|
|
36
|
+
justifyContent: "space-between",
|
|
37
|
+
alignItems: "center",
|
|
38
|
+
},
|
|
39
|
+
titleContainer: {
|
|
40
|
+
flex: 1,
|
|
41
|
+
marginRight: tokens.spacing.md,
|
|
42
|
+
},
|
|
43
|
+
title: {
|
|
44
|
+
fontWeight: "700",
|
|
45
|
+
},
|
|
46
|
+
details: {
|
|
47
|
+
gap: tokens.spacing.md,
|
|
48
|
+
paddingTop: tokens.spacing.md,
|
|
49
|
+
},
|
|
50
|
+
row: {
|
|
51
|
+
flexDirection: "row",
|
|
52
|
+
justifyContent: "space-between",
|
|
53
|
+
alignItems: "center",
|
|
54
|
+
gap: tokens.spacing.lg,
|
|
55
|
+
},
|
|
56
|
+
label: {
|
|
57
|
+
flex: 1,
|
|
58
|
+
},
|
|
59
|
+
value: {
|
|
60
|
+
fontWeight: "600",
|
|
61
|
+
textAlign: "right",
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
[tokens]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<View style={styles.container}>
|
|
69
|
+
<View style={styles.header}>
|
|
70
|
+
<View style={styles.titleContainer}>
|
|
71
|
+
<AtomicText
|
|
72
|
+
type="headlineSmall"
|
|
73
|
+
style={[styles.title, { color: tokens.colors.textPrimary }]}
|
|
74
|
+
>
|
|
75
|
+
{translations.title}
|
|
76
|
+
</AtomicText>
|
|
77
|
+
</View>
|
|
78
|
+
<PremiumStatusBadge
|
|
79
|
+
status={statusType}
|
|
80
|
+
activeLabel={translations.statusActive}
|
|
81
|
+
expiredLabel={translations.statusExpired}
|
|
82
|
+
noneLabel={translations.statusFree}
|
|
83
|
+
canceledLabel={translations.statusCanceled}
|
|
84
|
+
/>
|
|
85
|
+
</View>
|
|
67
86
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
{isPremium && (
|
|
88
|
+
<View style={styles.details}>
|
|
89
|
+
{isLifetime ? (
|
|
90
|
+
<DetailRow
|
|
91
|
+
label={translations.statusLabel}
|
|
92
|
+
value={translations.lifetimeLabel}
|
|
93
|
+
tokens={tokens}
|
|
94
|
+
styles={styles}
|
|
95
|
+
/>
|
|
96
|
+
) : (
|
|
97
|
+
<>
|
|
98
|
+
{expirationDate && (
|
|
99
|
+
<DetailRow
|
|
100
|
+
label={translations.expiresLabel}
|
|
101
|
+
value={expirationDate}
|
|
102
|
+
highlight={showExpiring}
|
|
103
|
+
tokens={tokens}
|
|
104
|
+
styles={styles}
|
|
105
|
+
/>
|
|
106
|
+
)}
|
|
107
|
+
{purchaseDate && (
|
|
108
|
+
<DetailRow
|
|
109
|
+
label={translations.purchasedLabel}
|
|
110
|
+
value={purchaseDate}
|
|
111
|
+
tokens={tokens}
|
|
112
|
+
styles={styles}
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
115
|
+
</>
|
|
116
|
+
)}
|
|
97
117
|
</View>
|
|
98
|
-
|
|
118
|
+
)}
|
|
119
|
+
</View>
|
|
120
|
+
);
|
|
99
121
|
};
|
|
100
122
|
|
|
101
123
|
interface DetailRowProps {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
124
|
+
label: string;
|
|
125
|
+
value: string;
|
|
126
|
+
highlight?: boolean;
|
|
127
|
+
tokens: ReturnType<typeof useAppDesignTokens>;
|
|
128
|
+
styles: {
|
|
129
|
+
row: object;
|
|
130
|
+
label: object;
|
|
131
|
+
value: object;
|
|
132
|
+
};
|
|
106
133
|
}
|
|
107
134
|
|
|
108
135
|
const DetailRow: React.FC<DetailRowProps> = ({
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
136
|
+
label,
|
|
137
|
+
value,
|
|
138
|
+
highlight,
|
|
139
|
+
tokens,
|
|
140
|
+
styles,
|
|
113
141
|
}) => (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
142
|
+
<View style={styles.row}>
|
|
143
|
+
<AtomicText
|
|
144
|
+
type="bodyMedium"
|
|
145
|
+
style={[styles.label, { color: tokens.colors.textSecondary }]}
|
|
146
|
+
>
|
|
147
|
+
{label}
|
|
148
|
+
</AtomicText>
|
|
149
|
+
<AtomicText
|
|
150
|
+
type="bodyMedium"
|
|
151
|
+
style={[
|
|
152
|
+
styles.value,
|
|
153
|
+
{ color: highlight ? tokens.colors.warning : tokens.colors.textPrimary },
|
|
154
|
+
]}
|
|
155
|
+
>
|
|
156
|
+
{value}
|
|
157
|
+
</AtomicText>
|
|
158
|
+
</View>
|
|
128
159
|
);
|
|
129
|
-
|
|
130
|
-
const styles = StyleSheet.create({
|
|
131
|
-
container: {
|
|
132
|
-
borderRadius: 16,
|
|
133
|
-
padding: 20,
|
|
134
|
-
gap: 16,
|
|
135
|
-
},
|
|
136
|
-
header: {
|
|
137
|
-
flexDirection: "row",
|
|
138
|
-
justifyContent: "space-between",
|
|
139
|
-
alignItems: "center",
|
|
140
|
-
},
|
|
141
|
-
titleContainer: {
|
|
142
|
-
flex: 1,
|
|
143
|
-
marginRight: 12,
|
|
144
|
-
},
|
|
145
|
-
title: {
|
|
146
|
-
fontWeight: "700",
|
|
147
|
-
},
|
|
148
|
-
details: {
|
|
149
|
-
gap: 12,
|
|
150
|
-
paddingTop: 12,
|
|
151
|
-
},
|
|
152
|
-
row: {
|
|
153
|
-
flexDirection: "row",
|
|
154
|
-
justifyContent: "space-between",
|
|
155
|
-
alignItems: "center",
|
|
156
|
-
gap: 16,
|
|
157
|
-
},
|
|
158
|
-
label: {
|
|
159
|
-
flex: 1,
|
|
160
|
-
},
|
|
161
|
-
value: {
|
|
162
|
-
fontWeight: "600",
|
|
163
|
-
textAlign: "right",
|
|
164
|
-
},
|
|
165
|
-
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscription Detail Types
|
|
3
|
+
* Type definitions for subscription detail screen and components
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { SubscriptionStatusType } from "../../domain/entities/SubscriptionStatus";
|
|
7
|
+
import type { CreditInfo } from "../components/details/PremiumDetailsCardTypes";
|
|
8
|
+
|
|
9
|
+
export type { SubscriptionStatusType, CreditInfo };
|
|
10
|
+
|
|
11
|
+
/** Translation strings for subscription detail screen */
|
|
12
|
+
export interface SubscriptionDetailTranslations {
|
|
13
|
+
title: string;
|
|
14
|
+
statusLabel: string;
|
|
15
|
+
statusActive: string;
|
|
16
|
+
statusExpired: string;
|
|
17
|
+
statusFree: string;
|
|
18
|
+
statusCanceled: string;
|
|
19
|
+
expiresLabel: string;
|
|
20
|
+
purchasedLabel: string;
|
|
21
|
+
lifetimeLabel: string;
|
|
22
|
+
creditsTitle: string;
|
|
23
|
+
remainingLabel: string;
|
|
24
|
+
usageTitle?: string;
|
|
25
|
+
manageButton: string;
|
|
26
|
+
upgradeButton: string;
|
|
27
|
+
creditsResetInfo?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Dev test action callbacks */
|
|
31
|
+
export interface DevTestActions {
|
|
32
|
+
onTestRenewal: () => Promise<void>;
|
|
33
|
+
onCheckCredits: () => void;
|
|
34
|
+
onTestDuplicate: () => Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Dev tools configuration */
|
|
38
|
+
export interface DevToolsConfig {
|
|
39
|
+
actions: DevTestActions;
|
|
40
|
+
title?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Configuration for subscription detail screen */
|
|
44
|
+
export interface SubscriptionDetailConfig {
|
|
45
|
+
statusType: SubscriptionStatusType;
|
|
46
|
+
isPremium: boolean;
|
|
47
|
+
expirationDate?: string | null;
|
|
48
|
+
purchaseDate?: string | null;
|
|
49
|
+
isLifetime?: boolean;
|
|
50
|
+
daysRemaining?: number | null;
|
|
51
|
+
willRenew?: boolean;
|
|
52
|
+
credits?: CreditInfo[];
|
|
53
|
+
translations: SubscriptionDetailTranslations;
|
|
54
|
+
onManageSubscription?: () => void;
|
|
55
|
+
onUpgrade?: () => void;
|
|
56
|
+
devTools?: DevToolsConfig;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Props for subscription detail screen */
|
|
60
|
+
export interface SubscriptionDetailScreenProps {
|
|
61
|
+
config: SubscriptionDetailConfig;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Props for subscription header component */
|
|
65
|
+
export interface SubscriptionHeaderProps {
|
|
66
|
+
statusType: SubscriptionStatusType;
|
|
67
|
+
isPremium: boolean;
|
|
68
|
+
isLifetime?: boolean;
|
|
69
|
+
expirationDate?: string | null;
|
|
70
|
+
purchaseDate?: string | null;
|
|
71
|
+
daysRemaining?: number | null;
|
|
72
|
+
translations: Pick<
|
|
73
|
+
SubscriptionDetailTranslations,
|
|
74
|
+
| "title"
|
|
75
|
+
| "statusLabel"
|
|
76
|
+
| "statusActive"
|
|
77
|
+
| "statusExpired"
|
|
78
|
+
| "statusFree"
|
|
79
|
+
| "statusCanceled"
|
|
80
|
+
| "expiresLabel"
|
|
81
|
+
| "purchasedLabel"
|
|
82
|
+
| "lifetimeLabel"
|
|
83
|
+
>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Props for credits list component */
|
|
87
|
+
export interface CreditsListProps {
|
|
88
|
+
credits: CreditInfo[];
|
|
89
|
+
title?: string;
|
|
90
|
+
description?: string;
|
|
91
|
+
remainingLabel?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Props for credit item component */
|
|
95
|
+
export interface CreditItemProps {
|
|
96
|
+
label: string;
|
|
97
|
+
current: number;
|
|
98
|
+
total: number;
|
|
99
|
+
remainingLabel?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Props for subscription actions component */
|
|
103
|
+
export interface SubscriptionActionsProps {
|
|
104
|
+
isPremium: boolean;
|
|
105
|
+
upgradeButtonLabel?: string;
|
|
106
|
+
onUpgrade?: () => void;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Props for dev test section */
|
|
110
|
+
export interface DevTestSectionProps {
|
|
111
|
+
actions: DevTestActions;
|
|
112
|
+
title?: string;
|
|
113
|
+
}
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Type definitions for subscription settings configuration
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type {
|
|
6
|
+
import type { SubscriptionStatusType } from "../../domain/entities/SubscriptionStatus";
|
|
7
|
+
import type { SubscriptionDetailConfig } from "./SubscriptionDetailTypes";
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
export type SubscriptionStatusType = "none" | "active" | "expired";
|
|
9
|
+
export type { SubscriptionStatusType };
|
|
10
10
|
|
|
11
11
|
/** Configuration for settings list item */
|
|
12
12
|
export interface SubscriptionSettingsItemConfig {
|