@umituz/react-native-subscription 2.27.122 → 2.27.124

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.
Files changed (33) hide show
  1. package/package.json +1 -1
  2. package/src/domains/credits/application/creditOperationUtils.ts +65 -144
  3. package/src/domains/credits/application/creditOperationUtils.types.ts +19 -0
  4. package/src/domains/credits/presentation/useCredits.ts +1 -11
  5. package/src/domains/paywall/components/PaywallModal.tsx +14 -107
  6. package/src/domains/paywall/components/PaywallModal.types.ts +26 -0
  7. package/src/domains/paywall/components/PlanCard.tsx +45 -148
  8. package/src/domains/paywall/components/PlanCard.types.ts +12 -0
  9. package/src/domains/subscription/application/SubscriptionSyncProcessor.ts +116 -0
  10. package/src/domains/subscription/application/SubscriptionSyncService.ts +10 -96
  11. package/src/domains/subscription/application/SubscriptionSyncUtils.ts +0 -2
  12. package/src/domains/subscription/core/SubscriptionConstants.ts +1 -13
  13. package/src/domains/subscription/infrastructure/hooks/usePurchasePackage.ts +4 -0
  14. package/src/domains/subscription/infrastructure/hooks/useRestorePurchase.ts +4 -0
  15. package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +7 -13
  16. package/src/domains/subscription/infrastructure/managers/SubscriptionManager.types.ts +15 -0
  17. package/src/domains/subscription/infrastructure/services/RevenueCatInitializer.ts +20 -5
  18. package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.tsx +13 -92
  19. package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.types.ts +47 -0
  20. package/src/domains/subscription/presentation/screens/components/UpgradePrompt.tsx +34 -126
  21. package/src/domains/subscription/presentation/screens/components/UpgradePrompt.types.ts +12 -0
  22. package/src/domains/subscription/presentation/usePremium.ts +3 -22
  23. package/src/domains/subscription/presentation/usePremium.types.ts +16 -0
  24. package/src/domains/subscription/presentation/useSubscriptionStatus.ts +30 -22
  25. package/src/domains/subscription/presentation/useSubscriptionStatus.types.ts +7 -0
  26. package/src/domains/wallet/presentation/hooks/useProductMetadata.ts +3 -16
  27. package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +1 -13
  28. package/src/domains/wallet/presentation/screens/WalletScreen.tsx +25 -112
  29. package/src/domains/wallet/presentation/screens/WalletScreen.types.ts +15 -0
  30. package/src/shared/utils/appValidators.ts +38 -0
  31. package/src/shared/utils/validators.ts +4 -122
  32. package/src/domains/paywall/components/README.md +0 -41
  33. package/src/domains/subscription/presentation/screens/README.md +0 -52
@@ -1,65 +1,20 @@
1
- /**
2
- * Wallet Screen
3
- *
4
- * Generic wallet screen composition.
5
- * Props-driven for full customization across apps.
6
- * No business logic - pure presentation.
7
- */
8
-
9
1
  import React from "react";
10
2
  import { View, StyleSheet, TouchableOpacity } from "react-native";
11
- import {
12
- useAppDesignTokens,
13
- AtomicText,
14
- AtomicIcon,
15
- AtomicSpinner,
16
- } from "@umituz/react-native-design-system";
3
+ import { useAppDesignTokens, AtomicText, AtomicIcon, AtomicSpinner } from "@umituz/react-native-design-system";
17
4
  import { ScreenLayout } from "../../../../shared/presentation";
18
5
  import { useNavigation } from "@react-navigation/native";
19
6
  import { useWallet } from "../hooks/useWallet";
20
7
  import { getWalletConfig } from "../../infrastructure/config/walletConfig";
21
- import {
22
- BalanceCard,
23
- type BalanceCardTranslations,
24
- } from "../components/BalanceCard";
25
- import {
26
- TransactionList,
27
- type TransactionListTranslations,
28
- } from "../components/TransactionList";
29
-
30
- export interface WalletScreenTranslations
31
- extends BalanceCardTranslations,
32
- TransactionListTranslations {
33
- screenTitle: string;
34
- }
35
-
36
- export interface WalletScreenProps {
37
- /** Translations (overrides global config) */
38
- translations?: WalletScreenTranslations;
39
- /** Override onBack handler (default: navigation.goBack) */
40
- onBack?: () => void;
41
- /** Custom date formatter */
42
- dateFormatter?: (timestamp: number) => string;
43
- /** Footer component */
44
- footer?: React.ReactNode;
45
- }
8
+ import { BalanceCard } from "../components/BalanceCard";
9
+ import { TransactionList } from "../components/TransactionList";
10
+ import { WalletScreenProps } from "./WalletScreen.types";
46
11
 
47
- export const WalletScreen: React.FC<WalletScreenProps> = ({
48
- translations,
49
- onBack,
50
- dateFormatter,
51
- footer,
52
- }) => {
12
+ export const WalletScreen: React.FC<WalletScreenProps> = ({ translations, onBack, dateFormatter, footer }) => {
53
13
  const tokens = useAppDesignTokens();
54
14
  const navigation = useNavigation();
55
15
  const config = getWalletConfig();
56
16
 
57
- const {
58
- balance,
59
- balanceLoading,
60
- transactions,
61
- transactionsLoading,
62
- } = useWallet({
17
+ const { balance, balanceLoading, transactions, transactionsLoading } = useWallet({
63
18
  transactionConfig: {
64
19
  collectionName: config.transactionCollection,
65
20
  useUserSubcollection: config.useUserSubcollection,
@@ -70,50 +25,6 @@ export const WalletScreen: React.FC<WalletScreenProps> = ({
70
25
  const activeTranslations = translations ?? config.translations;
71
26
  const handleBack = onBack ?? (() => navigation.goBack());
72
27
 
73
- const renderHeader = () => (
74
- <View style={[styles.header, { paddingTop: 12 }]}>
75
- <TouchableOpacity
76
- onPress={handleBack}
77
- style={styles.backButton}
78
- hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
79
- >
80
- <AtomicIcon
81
- name="arrow-left"
82
- size="lg"
83
- customColor={tokens.colors.textPrimary}
84
- />
85
- </TouchableOpacity>
86
- <AtomicText
87
- type="titleLarge"
88
- style={{ color: tokens.colors.textPrimary, fontWeight: "700" }}
89
- >
90
- {activeTranslations.screenTitle}
91
- </AtomicText>
92
- </View>
93
- );
94
-
95
- const renderBalance = () => {
96
- if (balanceLoading) {
97
- return (
98
- <AtomicSpinner
99
- size="xl"
100
- color="primary"
101
- text={activeTranslations.loading}
102
- fullContainer
103
- style={styles.loadingContainer}
104
- />
105
- );
106
- }
107
-
108
- return (
109
- <BalanceCard
110
- balance={balance}
111
- translations={activeTranslations}
112
- iconName={config.balanceIconName}
113
- />
114
- );
115
- };
116
-
117
28
  return (
118
29
  <ScreenLayout
119
30
  scrollable={true}
@@ -122,8 +33,21 @@ export const WalletScreen: React.FC<WalletScreenProps> = ({
122
33
  contentContainerStyle={styles.content}
123
34
  footer={footer}
124
35
  >
125
- {renderHeader()}
126
- {renderBalance()}
36
+ <View style={[styles.header, { paddingTop: 12 }]}>
37
+ <TouchableOpacity onPress={handleBack} style={styles.backButton} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}>
38
+ <AtomicIcon name="arrow-left" size="lg" customColor={tokens.colors.textPrimary} />
39
+ </TouchableOpacity>
40
+ <AtomicText type="titleLarge" style={{ color: tokens.colors.textPrimary, fontWeight: "700" }}>
41
+ {activeTranslations.screenTitle}
42
+ </AtomicText>
43
+ </View>
44
+
45
+ {balanceLoading ? (
46
+ <AtomicSpinner size="xl" color="primary" text={activeTranslations.loading} fullContainer style={styles.loadingContainer} />
47
+ ) : (
48
+ <BalanceCard balance={balance} translations={activeTranslations} iconName={config.balanceIconName} />
49
+ )}
50
+
127
51
  <TransactionList
128
52
  transactions={transactions}
129
53
  loading={transactionsLoading}
@@ -135,19 +59,8 @@ export const WalletScreen: React.FC<WalletScreenProps> = ({
135
59
  };
136
60
 
137
61
  const styles = StyleSheet.create({
138
- content: {
139
- paddingBottom: 24,
140
- },
141
- header: {
142
- flexDirection: "row",
143
- alignItems: "center",
144
- paddingHorizontal: 16,
145
- paddingBottom: 12,
146
- },
147
- backButton: {
148
- marginRight: 16,
149
- },
150
- loadingContainer: {
151
- minHeight: 200,
152
- },
62
+ content: { paddingBottom: 24 },
63
+ header: { flexDirection: "row", alignItems: "center", paddingHorizontal: 16, paddingBottom: 12 },
64
+ backButton: { marginRight: 16 },
65
+ loadingContainer: { minHeight: 200 },
153
66
  });
@@ -0,0 +1,15 @@
1
+ import type { BalanceCardTranslations } from "../components/BalanceCard";
2
+ import type { TransactionListTranslations } from "../components/TransactionList";
3
+
4
+ export interface WalletScreenTranslations
5
+ extends BalanceCardTranslations,
6
+ TransactionListTranslations {
7
+ screenTitle: string;
8
+ }
9
+
10
+ export interface WalletScreenProps {
11
+ translations?: WalletScreenTranslations;
12
+ onBack?: () => void;
13
+ dateFormatter?: (timestamp: number) => string;
14
+ footer?: React.ReactNode;
15
+ }
@@ -0,0 +1,38 @@
1
+ import {
2
+ isNonEmptyString,
3
+ isValidNumber,
4
+ isPositiveNumber,
5
+ isNonNegativeNumber,
6
+ isInteger,
7
+ } from "./validators";
8
+
9
+ export function isValidCreditAmount(value: unknown): value is number {
10
+ return isInteger(value) && isNonNegativeNumber(value);
11
+ }
12
+
13
+ export function isValidPrice(value: unknown): value is number {
14
+ if (!isPositiveNumber(value)) return false;
15
+ const decimalPlaces = (value.toString().split(".")[1] || "").length;
16
+ return decimalPlaces <= 2;
17
+ }
18
+
19
+ export function isValidProductId(value: unknown): value is string {
20
+ return isNonEmptyString(value) && /^[a-zA-Z0-9._-]+$/.test(value);
21
+ }
22
+
23
+ export function isValidUserId(value: unknown): value is string {
24
+ return isNonEmptyString(value);
25
+ }
26
+
27
+ export function sanitizeString(value: unknown): string | null {
28
+ if (value === null || value === undefined) return null;
29
+ return String(value).trim().replace(/\s+/g, " ");
30
+ }
31
+
32
+ export function sanitizeNumber(value: unknown, defaultValue: number = 0): number {
33
+ return isValidNumber(value) ? value : defaultValue;
34
+ }
35
+
36
+ export function isOneOf<T>(value: unknown, allowedValues: readonly T[]): value is T {
37
+ return allowedValues.includes(value as T);
38
+ }
@@ -1,46 +1,23 @@
1
- /**
2
- * Shared Validation Utilities
3
- * Common validation functions and type guards
4
- */
5
-
6
- /**
7
- * Check if value is a non-empty string
8
- */
9
1
  export function isNonEmptyString(value: unknown): value is string {
10
2
  return typeof value === "string" && value.trim().length > 0;
11
3
  }
12
4
 
13
- /**
14
- * Check if value is a valid number (not NaN or Infinity)
15
- */
16
5
  export function isValidNumber(value: unknown): value is number {
17
6
  return typeof value === "number" && !isNaN(value) && isFinite(value);
18
7
  }
19
8
 
20
- /**
21
- * Check if value is a positive number
22
- */
23
9
  export function isPositiveNumber(value: unknown): value is number {
24
10
  return isValidNumber(value) && value > 0;
25
11
  }
26
12
 
27
- /**
28
- * Check if value is a non-negative number
29
- */
30
13
  export function isNonNegativeNumber(value: unknown): value is number {
31
14
  return isValidNumber(value) && value >= 0;
32
15
  }
33
16
 
34
- /**
35
- * Check if value is a valid integer
36
- */
37
17
  export function isInteger(value: unknown): value is number {
38
18
  return isValidNumber(value) && Number.isInteger(value);
39
19
  }
40
20
 
41
- /**
42
- * Check if value is a valid date
43
- */
44
21
  export function isValidDate(value: unknown): boolean {
45
22
  if (value instanceof Date) {
46
23
  return !isNaN(value.getTime());
@@ -52,24 +29,14 @@ export function isValidDate(value: unknown): boolean {
52
29
  return false;
53
30
  }
54
31
 
55
- /**
56
- * Check if value is a valid email (basic validation)
57
- */
58
32
  export function isValidEmail(value: unknown): value is string {
59
- if (!isNonEmptyString(value)) {
60
- return false;
61
- }
33
+ if (!isNonEmptyString(value)) return false;
62
34
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
63
35
  return emailRegex.test(value);
64
36
  }
65
37
 
66
- /**
67
- * Check if value is a valid URL (basic validation)
68
- */
69
38
  export function isValidUrl(value: unknown): value is string {
70
- if (!isNonEmptyString(value)) {
71
- return false;
72
- }
39
+ if (!isNonEmptyString(value)) return false;
73
40
  try {
74
41
  new URL(value);
75
42
  return true;
@@ -78,110 +45,25 @@ export function isValidUrl(value: unknown): value is string {
78
45
  }
79
46
  }
80
47
 
81
- /**
82
- * Check if value is within a numeric range (type guard version)
83
- */
84
48
  export function isValueInRange(
85
49
  value: unknown,
86
50
  min: number,
87
51
  max: number,
88
52
  inclusive: boolean = true
89
53
  ): value is number {
90
- if (!isValidNumber(value)) {
91
- return false;
92
- }
93
- if (inclusive) {
94
- return value >= min && value <= max;
95
- }
54
+ if (!isValidNumber(value)) return false;
55
+ if (inclusive) return value >= min && value <= max;
96
56
  return value > min && value < max;
97
57
  }
98
58
 
99
- /**
100
- * Check if array has at least one element
101
- */
102
59
  export function isNonEmptyArray<T>(value: unknown): value is [T, ...T[]] {
103
60
  return Array.isArray(value) && value.length > 0;
104
61
  }
105
62
 
106
- /**
107
- * Check if value is a plain object (not null, not array)
108
- */
109
63
  export function isPlainObject(value: unknown): value is Record<string, unknown> {
110
64
  return typeof value === "object" && value !== null && !Array.isArray(value);
111
65
  }
112
66
 
113
- /**
114
- * Check if value is defined (not null or undefined)
115
- */
116
67
  export function isDefined<T>(value: T | null | undefined): value is T {
117
68
  return value !== null && value !== undefined;
118
69
  }
119
-
120
- /**
121
- * Validate that a number is within credit limits
122
- */
123
- export function isValidCreditAmount(value: unknown): value is number {
124
- return isInteger(value) && isNonNegativeNumber(value);
125
- }
126
-
127
- /**
128
- * Validate that a price is valid (positive number with max 2 decimal places)
129
- */
130
- export function isValidPrice(value: unknown): value is number {
131
- if (!isPositiveNumber(value)) {
132
- return false;
133
- }
134
- // Check for max 2 decimal places
135
- const decimalPlaces = (value.toString().split(".")[1] || "").length;
136
- return decimalPlaces <= 2;
137
- }
138
-
139
- /**
140
- * Check if a string is a valid product identifier
141
- */
142
- export function isValidProductId(value: unknown): value is string {
143
- return isNonEmptyString(value) && /^[a-zA-Z0-9._-]+$/.test(value);
144
- }
145
-
146
- /**
147
- * Check if value is a valid user ID
148
- */
149
- export function isValidUserId(value: unknown): value is string {
150
- return isNonEmptyString(value) && value.length > 0;
151
- }
152
-
153
- /**
154
- * Sanitize string input (trim and remove extra whitespace)
155
- */
156
- export function sanitizeString(value: unknown): string | null {
157
- if (value === null || value === undefined) {
158
- return null;
159
- }
160
- if (typeof value === "string") {
161
- return value.trim().replace(/\s+/g, " ");
162
- }
163
- return String(value).trim().replace(/\s+/g, " ");
164
- }
165
-
166
- /**
167
- * Validate and sanitize a number input
168
- */
169
- export function sanitizeNumber(
170
- value: unknown,
171
- defaultValue: number = 0
172
- ): number {
173
- if (isValidNumber(value)) {
174
- return value;
175
- }
176
- return defaultValue;
177
- }
178
-
179
- /**
180
- * Check if a value is within an allowed set of values
181
- */
182
- export function isOneOf<T>(
183
- value: unknown,
184
- allowedValues: readonly T[]
185
- ): value is T {
186
- return allowedValues.includes(value as T);
187
- }
@@ -1,41 +0,0 @@
1
- # Paywall Components
2
-
3
- ## Location
4
- UI components for displaying paywalls and upgrade prompts.
5
-
6
- ## Strategy
7
- This directory contains React Native components for rendering paywall screens and upgrade prompts with clear value communication and smooth purchase flow.
8
-
9
- ## Restrictions
10
-
11
- ### REQUIRED
12
- - Must communicate premium benefits clearly
13
- - Must highlight recommended package
14
- - Must allow dismissal when appropriate
15
- - Must show appropriate loading indicators
16
-
17
- ### PROHIBITED
18
- - DO NOT obscure purchase flow
19
- - DO NOT hide pricing information
20
- - DO NOT prevent appropriate dismissal
21
- - DO NOT show technical errors to users
22
-
23
- ### CRITICAL SAFETY
24
- - Pricing information MUST be clear and accurate
25
- - Purchase flow MUST be simple and straightforward
26
- - Loading states MUST be visible during operations
27
- - Purchase failures MUST be handled gracefully
28
-
29
- ## AI Agent Guidelines
30
- 1. Communicate premium benefits clearly to users
31
- 2. Highlight recommended package with visual hierarchy
32
- 3. Show social proof (user counts, testimonials) when available
33
- 4. Allow users to dismiss paywall when appropriate
34
- 5. Make purchase flow simple and straightforward
35
- 6. Show loading indicators during async operations
36
- 7. Handle purchase failures with user-friendly messages
37
-
38
- ## Related Documentation
39
- - [Paywall README](../README.md)
40
- - [PaywallVisibility Hook](../../hooks/usePaywallVisibility.md)
41
- - [Premium Components](../../components/details/README.md)
@@ -1,52 +0,0 @@
1
- # Screens
2
-
3
- Tam ekran UI bileşenleri ve ekranlar.
4
-
5
- ## Location
6
-
7
- `src/presentation/screens/`
8
-
9
- ## Strategy
10
-
11
- Tam ekran kullanıcı arayüzü bileşenleri ve navigasyon akışlarını içerir. Abonelik detaylarını gösterir ve yönetim işlevleri sağlar.
12
-
13
- ## Restrictions
14
-
15
- ### REQUIRED
16
-
17
- - MUST integrate properly with React Navigation
18
- - MUST provide appropriate headers and navigation
19
- - MUST handle loading states gracefully
20
- - MUST handle error states gracefully
21
- - MUST support back navigation
22
- - MUST be responsive across different screen sizes
23
-
24
- ### PROHIBITED
25
-
26
- - MUST NOT bypass navigation stack
27
- - MUST NOT create navigation dead-ends
28
- - MUST NOT block user from navigating away
29
- - MUST NOT hardcode navigation routes
30
-
31
- ### CRITICAL
32
-
33
- - Always provide clear navigation paths
34
- - Handle all loading and error states
35
- - Ensure proper back button functionality
36
- - Support deep linking when applicable
37
- - Maintain consistent styling with rest of app
38
-
39
- ## AI Agent Guidelines
40
-
41
- When working with screens:
42
- 1. Navigation - screen'i doğru navigation stack'e ekleyin
43
- 2. Header - uygun başlık ve stiller kullanın
44
- 3. Back Button - kullanıcının geri dönmesini sağlayın
45
- 4. Loading - yükleme durumlarını gösterin
46
- 5. Error - hata durumlarını graceful handle edin
47
-
48
- ## Related Documentation
49
-
50
- - [Presentation Layer](../README.md)
51
- - [Components](../components/README.md)
52
- - [Hooks](../hooks/README.md)