@umituz/react-native-subscription 2.27.123 → 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.
- package/package.json +1 -1
- package/src/domains/credits/application/creditOperationUtils.ts +65 -144
- package/src/domains/credits/application/creditOperationUtils.types.ts +19 -0
- package/src/domains/credits/presentation/useCredits.ts +1 -11
- package/src/domains/paywall/components/PaywallModal.tsx +14 -107
- package/src/domains/paywall/components/PaywallModal.types.ts +26 -0
- package/src/domains/paywall/components/PlanCard.tsx +45 -148
- package/src/domains/paywall/components/PlanCard.types.ts +12 -0
- package/src/domains/subscription/application/SubscriptionSyncProcessor.ts +116 -0
- package/src/domains/subscription/application/SubscriptionSyncService.ts +10 -96
- package/src/domains/subscription/application/SubscriptionSyncUtils.ts +0 -2
- package/src/domains/subscription/core/SubscriptionConstants.ts +1 -13
- package/src/domains/subscription/infrastructure/managers/SubscriptionManager.ts +7 -13
- package/src/domains/subscription/infrastructure/managers/SubscriptionManager.types.ts +15 -0
- package/src/domains/subscription/infrastructure/services/RevenueCatInitializer.ts +20 -5
- package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.tsx +13 -92
- package/src/domains/subscription/presentation/screens/SubscriptionDetailScreen.types.ts +47 -0
- package/src/domains/subscription/presentation/screens/components/UpgradePrompt.tsx +34 -126
- package/src/domains/subscription/presentation/screens/components/UpgradePrompt.types.ts +12 -0
- package/src/domains/subscription/presentation/usePremium.ts +3 -22
- package/src/domains/subscription/presentation/usePremium.types.ts +16 -0
- package/src/domains/subscription/presentation/useSubscriptionStatus.ts +30 -22
- package/src/domains/subscription/presentation/useSubscriptionStatus.types.ts +7 -0
- package/src/domains/wallet/presentation/hooks/useProductMetadata.ts +3 -16
- package/src/domains/wallet/presentation/hooks/useTransactionHistory.ts +1 -13
- package/src/domains/wallet/presentation/screens/WalletScreen.tsx +25 -112
- package/src/domains/wallet/presentation/screens/WalletScreen.types.ts +15 -0
- package/src/shared/utils/appValidators.ts +38 -0
- package/src/shared/utils/validators.ts +4 -122
- package/src/domains/paywall/components/README.md +0 -41
- 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
|
-
|
|
23
|
-
|
|
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
|
-
{
|
|
126
|
-
|
|
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
|
-
|
|
140
|
-
},
|
|
141
|
-
|
|
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
|
-
|
|
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)
|