@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-subscription",
3
- "version": "2.14.17",
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 { type DevTestActions } from "./presentation/screens/components/DevTestSection";
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 || "Lifetime"}
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 '../../../domain/entities/SubscriptionStatus';
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 colors: Record<SubscriptionStatusType, string> = {
40
- active: tokens.colors.success,
41
- expired: tokens.colors.error,
42
- none: tokens.colors.textTertiary,
43
- canceled: tokens.colors.warning,
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 backgroundColor = colors[status];
47
- const label = labels[status];
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={[styles.badge, { backgroundColor }]}>
51
- <AtomicText type="labelSmall" style={[styles.badgeText, { color: tokens.colors.onPrimary }]}>
52
- {label}
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, type DevTestActions } from "./components/DevTestSection";
14
- import type { SubscriptionStatusType } from "../components/details/PremiumStatusBadge";
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 interface SubscriptionDetailTranslations {
18
- title: string;
19
- statusLabel: string;
20
- statusActive: string;
21
- statusExpired: string;
22
- statusFree: string;
23
- statusCanceled: string;
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 interface SubscriptionDetailConfig {
36
- statusType: SubscriptionStatusType;
37
- isPremium: boolean;
38
- expirationDate?: string | null;
39
- purchaseDate?: string | null;
40
- isLifetime?: boolean;
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
- export interface SubscriptionDetailScreenProps {
54
- config: SubscriptionDetailConfig;
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
- export const SubscriptionDetailScreen: React.FC<
58
- SubscriptionDetailScreenProps
59
- > = ({ config }) => {
60
- const tokens = useAppDesignTokens();
61
- const showCredits = config.credits && config.credits.length > 0;
62
- const showUpgradeButton = !config.isPremium && config.onUpgrade;
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
- const styles = useMemo(
65
- () =>
66
- StyleSheet.create({
67
- content: {
68
- flexGrow: 1,
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
- <View style={styles.cardsContainer}>
99
- <SubscriptionHeader
100
- statusType={config.statusType}
101
- isPremium={config.isPremium}
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
- <View style={styles.spacer} />
88
+ <View style={styles.spacer} />
122
89
 
123
- {showUpgradeButton && (
124
- <SubscriptionActions
125
- isPremium={config.isPremium}
126
- upgradeButtonLabel={config.translations.upgradeButton}
127
- onUpgrade={config.onUpgrade}
128
- />
129
- )}
130
- </ScreenLayout>
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
- label,
19
- current,
20
- total,
21
- remainingLabel = "remaining",
12
+ label,
13
+ current,
14
+ total,
15
+ remainingLabel,
22
16
  }) => {
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;
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
- const getColor = () => {
29
- if (isLow) return tokens.colors.error;
30
- if (isMedium) return tokens.colors.warning;
31
- return tokens.colors.success;
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
- return (
35
- <View style={styles.container}>
36
- <View style={styles.header}>
37
- <AtomicText type="bodyMedium" style={[styles.label, { color: tokens.colors.textPrimary }]}>
38
- {label}
39
- </AtomicText>
40
- <View style={[styles.badge, { backgroundColor: tokens.colors.surfaceSecondary }]}>
41
- <AtomicText type="labelSmall" style={[styles.count, { color: getColor() }]}>
42
- {current} / {total}
43
- </AtomicText>
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
- <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, Text, StyleSheet } from "react-native";
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 { CreditInfo } from "../../components/details/PremiumDetailsCard";
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
- credits,
21
- title,
22
- description,
23
- remainingLabel,
13
+ credits,
14
+ title,
15
+ description,
16
+ remainingLabel,
24
17
  }) => {
25
- const tokens = useAppDesignTokens();
18
+ const tokens = useAppDesignTokens();
26
19
 
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
- };
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
- 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
- });
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 interface DevTestActions {
12
- onTestRenewal: () => Promise<void>;
13
- onCheckCredits: () => void;
14
- onTestDuplicate: () => Promise<void>;
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
- actions: DevTestActions;
19
- title?: string;
22
+ export interface DevTestSectionWithTranslationsProps extends DevTestSectionProps {
23
+ translations?: DevTestTranslations;
20
24
  }
21
25
 
22
- export const DevTestSection: React.FC<DevTestSectionProps> = ({
26
+ export const DevTestSection: React.FC<DevTestSectionWithTranslationsProps> = ({
23
27
  actions,
24
- title = "🧪 Developer Testing",
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
- style={[
35
- styles.container,
36
- {
37
- backgroundColor: tokens.colors.surfaceSecondary,
38
- borderTopColor: tokens.colors.border,
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, { backgroundColor: tokens.colors.primary }]}
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
- ⚡ Test Auto-Renewal (Add Credits)
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
- 📊 Check Current Credits
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
- 🔒 Test Duplicate Protection
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
- isPremium,
18
- upgradeButtonLabel,
19
- onUpgrade,
12
+ isPremium,
13
+ upgradeButtonLabel,
14
+ onUpgrade,
20
15
  }) => {
21
- const tokens = useAppDesignTokens();
16
+ const tokens = useAppDesignTokens();
22
17
 
23
- const styles = useMemo(
24
- () =>
25
- StyleSheet.create({
26
- container: {
27
- paddingBottom: tokens.spacing.xl,
28
- },
29
- primaryButton: {
30
- paddingVertical: tokens.spacing.md,
31
- borderRadius: tokens.borderRadius.lg,
32
- alignItems: "center",
33
- backgroundColor: tokens.colors.primary,
34
- },
35
- buttonText: {
36
- color: tokens.colors.onPrimary,
37
- fontWeight: "700",
38
- },
39
- }),
40
- [tokens]
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
- if (isPremium || !onUpgrade || !upgradeButtonLabel) {
44
- return null;
45
- }
38
+ if (isPremium || !onUpgrade || !upgradeButtonLabel) {
39
+ return null;
40
+ }
46
41
 
47
- return (
48
- <View style={styles.container}>
49
- <TouchableOpacity style={styles.primaryButton} onPress={onUpgrade}>
50
- <AtomicText type="titleMedium" style={styles.buttonText}>
51
- {upgradeButtonLabel}
52
- </AtomicText>
53
- </TouchableOpacity>
54
- </View>
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
- 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
- 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
- statusType,
38
- isPremium,
39
- isLifetime,
40
- expirationDate,
41
- purchaseDate,
42
- daysRemaining,
43
- translations,
13
+ statusType,
14
+ isPremium,
15
+ isLifetime,
16
+ expirationDate,
17
+ purchaseDate,
18
+ daysRemaining,
19
+ translations,
44
20
  }) => {
45
- const tokens = useAppDesignTokens();
46
- const showExpiring =
47
- daysRemaining !== null &&
48
- daysRemaining !== undefined &&
49
- daysRemaining <= 7;
21
+ const tokens = useAppDesignTokens();
22
+ const showExpiring =
23
+ daysRemaining !== null && daysRemaining !== undefined && daysRemaining <= 7;
50
24
 
51
- return (
52
- <View style={[styles.container, { backgroundColor: tokens.colors.surface }]}>
53
- <View style={styles.header}>
54
- <View style={styles.titleContainer}>
55
- <AtomicText type="headlineSmall" style={[styles.title, { color: tokens.colors.textPrimary }]}>
56
- {translations.title}
57
- </AtomicText>
58
- </View>
59
- <PremiumStatusBadge
60
- status={statusType}
61
- activeLabel={translations.statusActive}
62
- expiredLabel={translations.statusExpired}
63
- noneLabel={translations.statusFree}
64
- canceledLabel={translations.statusCanceled}
65
- />
66
- </View>
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
- {isPremium && (
69
- <View style={styles.details}>
70
- {isLifetime ? (
71
- <DetailRow
72
- label={translations.statusLabel}
73
- value={translations.lifetimeLabel}
74
- tokens={tokens}
75
- />
76
- ) : (
77
- <>
78
- {expirationDate && (
79
- <DetailRow
80
- label={translations.expiresLabel}
81
- value={expirationDate}
82
- highlight={showExpiring}
83
- tokens={tokens}
84
- />
85
- )}
86
- {purchaseDate && (
87
- <DetailRow
88
- label={translations.purchasedLabel}
89
- value={purchaseDate}
90
- tokens={tokens}
91
- />
92
- )}
93
- </>
94
- )}
95
- </View>
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
- label: string;
103
- value: string;
104
- highlight?: boolean;
105
- tokens: ReturnType<typeof useAppDesignTokens>;
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
- label,
110
- value,
111
- highlight,
112
- tokens,
136
+ label,
137
+ value,
138
+ highlight,
139
+ tokens,
140
+ styles,
113
141
  }) => (
114
- <View style={styles.row}>
115
- <AtomicText type="bodyMedium" style={[styles.label, { color: tokens.colors.textSecondary }]}>
116
- {label}
117
- </AtomicText>
118
- <AtomicText
119
- type="bodyMedium"
120
- style={[
121
- styles.value,
122
- { color: highlight ? tokens.colors.warning : tokens.colors.textPrimary },
123
- ]}
124
- >
125
- {value}
126
- </AtomicText>
127
- </View>
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 { SubscriptionDetailConfig } from "../screens/SubscriptionDetailScreen";
6
+ import type { SubscriptionStatusType } from "../../domain/entities/SubscriptionStatus";
7
+ import type { SubscriptionDetailConfig } from "./SubscriptionDetailTypes";
7
8
 
8
- /** Status type for subscription state */
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 {