@fadyshawky/react-native-magic 1.0.8 → 2.0.0
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/.vscode/settings.json +7 -0
- package/package.json +1 -1
- package/template/App.tsx +30 -9
- package/template/package-lock.json +181 -123
- package/template/package.json +2 -0
- package/template/src/common/ImageResources.g.ts +3 -5
- package/template/src/common/components/Background.tsx +66 -28
- package/template/src/common/components/Cards.tsx +116 -0
- package/template/src/common/components/Container.tsx +145 -0
- package/template/src/common/components/FlatListWrapper.tsx +1 -0
- package/template/src/common/components/ImageCropPickerButton.tsx +1 -1
- package/template/src/common/components/OTPInput.tsx +107 -0
- package/template/src/common/components/PhotoTakingButton.tsx +1 -4
- package/template/src/common/components/PrimaryButton.tsx +171 -157
- package/template/src/common/components/RTLAwareText.tsx +42 -0
- package/template/src/common/components/RTLAwareView.tsx +179 -0
- package/template/src/common/components/RadioButton.tsx +1 -3
- package/template/src/common/components/RadioIcon.tsx +1 -2
- package/template/src/common/components/SearchBar.tsx +179 -0
- package/template/src/common/components/Separator.tsx +7 -4
- package/template/src/common/components/TouchablePlatform.tsx +1 -3
- package/template/src/common/components/TryAgain.tsx +3 -3
- package/template/src/common/helpers/inAppReviewHelper.ts +0 -1
- package/template/src/common/helpers/stringsHelpers.ts +10 -0
- package/template/src/common/hooks/useFlatListActions.ts +1 -1
- package/template/src/common/localization/LocalizationProvider.tsx +152 -0
- package/template/src/common/localization/README.md +488 -0
- package/template/src/common/localization/localization.ts +12 -0
- package/template/src/common/localization/translations/profileLocalization.ts +24 -0
- package/template/src/common/validations/errorValidations.ts +1 -6
- package/template/src/common/validations/examples/TextInputWithValidation.tsx +229 -0
- package/template/src/common/validations/index.ts +28 -0
- package/template/src/common/validations/regex.js +83 -0
- package/template/src/common/validations/regexValidator.ts +240 -0
- package/template/src/common/validations/validationConstants.ts +2 -2
- package/template/src/core/api/errorHandler.ts +39 -0
- package/template/src/core/api/responseHandlers.ts +1 -26
- package/template/src/core/api/serverHeaders.ts +13 -23
- package/template/src/core/store/app/appSlice.ts +1 -2
- package/template/src/core/theme/ThemeProvider.tsx +63 -0
- package/template/src/core/theme/colors.ts +31 -42
- package/template/src/core/theme/commonConsts.ts +1 -1
- package/template/src/core/theme/commonStyles.ts +267 -210
- package/template/src/core/theme/fonts.ts +17 -1
- package/template/src/core/theme/scaling.ts +101 -0
- package/template/src/core/theme/themes.ts +214 -0
- package/template/src/core/theme/types.ts +51 -0
- package/template/src/navigation/AuthStack.tsx +25 -30
- package/template/src/navigation/HeaderComponents.tsx +18 -58
- package/template/src/navigation/MainNavigation.tsx +5 -6
- package/template/src/navigation/MainStack.tsx +3 -28
- package/template/src/navigation/RootNavigation.tsx +1 -7
- package/template/src/navigation/TabBar.tsx +2 -2
- package/template/src/navigation/TopTabBar.tsx +1 -1
- package/template/src/screens/Login/Login.tsx +3 -3
- package/template/src/screens/home/components/CarouselSection.tsx +7 -8
- package/template/src/screens/home/components/FeaturedCarousel.tsx +5 -6
- package/template/src/screens/registration/RegistrationScreen.tsx +2 -2
- package/template/src/screens/resetPassword/ForgotPasswordScreen.tsx +2 -2
- package/template/src/utils/stringBuilder.js +25 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import React, {useState} from 'react';
|
|
2
|
+
import {View, TextInput, Text, StyleSheet} from 'react-native';
|
|
3
|
+
import {
|
|
4
|
+
regexValidation,
|
|
5
|
+
patterns,
|
|
6
|
+
ValidationResult,
|
|
7
|
+
requiredValidation,
|
|
8
|
+
runValidations,
|
|
9
|
+
} from '../regexValidator';
|
|
10
|
+
|
|
11
|
+
interface TextInputWithValidationProps {
|
|
12
|
+
label: string;
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
value: string;
|
|
15
|
+
onChangeText: (text: string) => void;
|
|
16
|
+
secureTextEntry?: boolean;
|
|
17
|
+
validationType?:
|
|
18
|
+
| 'email'
|
|
19
|
+
| 'password'
|
|
20
|
+
| 'phone'
|
|
21
|
+
| 'name'
|
|
22
|
+
| 'username'
|
|
23
|
+
| 'numeric'
|
|
24
|
+
| 'price'
|
|
25
|
+
| 'custom';
|
|
26
|
+
customRegex?: RegExp;
|
|
27
|
+
customErrorMessage?: string;
|
|
28
|
+
required?: boolean;
|
|
29
|
+
minLength?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A reusable text input component with built-in validation
|
|
34
|
+
*/
|
|
35
|
+
const TextInputWithValidation: React.FC<TextInputWithValidationProps> = ({
|
|
36
|
+
label,
|
|
37
|
+
placeholder,
|
|
38
|
+
value,
|
|
39
|
+
onChangeText,
|
|
40
|
+
secureTextEntry = false,
|
|
41
|
+
validationType = 'custom',
|
|
42
|
+
customRegex,
|
|
43
|
+
customErrorMessage,
|
|
44
|
+
required = false,
|
|
45
|
+
minLength,
|
|
46
|
+
}) => {
|
|
47
|
+
const [touched, setTouched] = useState(false);
|
|
48
|
+
const [validationResult, setValidationResult] = useState<ValidationResult>({
|
|
49
|
+
isValid: true,
|
|
50
|
+
message: '',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Get the appropriate regex based on validation type
|
|
54
|
+
const getRegexForType = (): RegExp => {
|
|
55
|
+
switch (validationType) {
|
|
56
|
+
case 'email':
|
|
57
|
+
return patterns.EMAIL;
|
|
58
|
+
case 'password':
|
|
59
|
+
return patterns.PASSWORD;
|
|
60
|
+
case 'phone':
|
|
61
|
+
return patterns.PHONE;
|
|
62
|
+
case 'name':
|
|
63
|
+
return patterns.NAME;
|
|
64
|
+
case 'username':
|
|
65
|
+
return patterns.USERNAME;
|
|
66
|
+
case 'numeric':
|
|
67
|
+
return patterns.NUMERIC;
|
|
68
|
+
case 'price':
|
|
69
|
+
return patterns.PRICE;
|
|
70
|
+
case 'custom':
|
|
71
|
+
return customRegex || /^.*$/; // Allow anything if no custom regex
|
|
72
|
+
default:
|
|
73
|
+
return /^.*$/;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Get the default error message based on validation type
|
|
78
|
+
const getDefaultErrorMessage = (): string => {
|
|
79
|
+
switch (validationType) {
|
|
80
|
+
case 'email':
|
|
81
|
+
return 'Please enter a valid email address';
|
|
82
|
+
case 'password':
|
|
83
|
+
return 'Password must be at least 8 characters with uppercase, lowercase, and number';
|
|
84
|
+
case 'phone':
|
|
85
|
+
return 'Please enter a valid phone number';
|
|
86
|
+
case 'name':
|
|
87
|
+
return 'Please enter a valid name';
|
|
88
|
+
case 'username':
|
|
89
|
+
return 'Username must be 3-20 characters (letters, numbers, underscores)';
|
|
90
|
+
case 'numeric':
|
|
91
|
+
return 'Please enter numbers only';
|
|
92
|
+
case 'price':
|
|
93
|
+
return 'Please enter a valid price (e.g., 10.99)';
|
|
94
|
+
default:
|
|
95
|
+
return customErrorMessage || 'Invalid input';
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Validate the input when it loses focus
|
|
100
|
+
const handleBlur = () => {
|
|
101
|
+
setTouched(true);
|
|
102
|
+
|
|
103
|
+
// Build validations array
|
|
104
|
+
const validations = [];
|
|
105
|
+
|
|
106
|
+
// Add required validation if needed
|
|
107
|
+
if (required) {
|
|
108
|
+
validations.push((val: string) =>
|
|
109
|
+
requiredValidation(val, 'This field is required'),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Add pattern validation if it's not empty
|
|
114
|
+
validations.push((val: string) => {
|
|
115
|
+
// Skip empty validation if not required
|
|
116
|
+
if (!required && (!val || val.trim() === '')) {
|
|
117
|
+
return {isValid: true, message: ''};
|
|
118
|
+
}
|
|
119
|
+
return regexValidation(val, getRegexForType(), getDefaultErrorMessage());
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Run all validations
|
|
123
|
+
const result = runValidations(value, validations);
|
|
124
|
+
setValidationResult(result);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<View style={styles.container}>
|
|
129
|
+
<Text style={styles.label}>{label}</Text>
|
|
130
|
+
<TextInput
|
|
131
|
+
style={[
|
|
132
|
+
styles.input,
|
|
133
|
+
touched && !validationResult.isValid && styles.inputError,
|
|
134
|
+
]}
|
|
135
|
+
placeholder={placeholder}
|
|
136
|
+
value={value}
|
|
137
|
+
onChangeText={onChangeText}
|
|
138
|
+
onBlur={handleBlur}
|
|
139
|
+
secureTextEntry={secureTextEntry}
|
|
140
|
+
/>
|
|
141
|
+
{touched && !validationResult.isValid && (
|
|
142
|
+
<Text style={styles.errorText}>{validationResult.message}</Text>
|
|
143
|
+
)}
|
|
144
|
+
</View>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const styles = StyleSheet.create({
|
|
149
|
+
container: {
|
|
150
|
+
marginBottom: 16,
|
|
151
|
+
},
|
|
152
|
+
label: {
|
|
153
|
+
fontSize: 16,
|
|
154
|
+
marginBottom: 8,
|
|
155
|
+
fontWeight: '500',
|
|
156
|
+
},
|
|
157
|
+
input: {
|
|
158
|
+
borderWidth: 1,
|
|
159
|
+
borderColor: '#ccc',
|
|
160
|
+
borderRadius: 4,
|
|
161
|
+
paddingHorizontal: 12,
|
|
162
|
+
paddingVertical: 8,
|
|
163
|
+
fontSize: 16,
|
|
164
|
+
},
|
|
165
|
+
inputError: {
|
|
166
|
+
borderColor: '#ff3b30',
|
|
167
|
+
},
|
|
168
|
+
errorText: {
|
|
169
|
+
color: '#ff3b30',
|
|
170
|
+
fontSize: 14,
|
|
171
|
+
marginTop: 4,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
export default TextInputWithValidation;
|
|
176
|
+
|
|
177
|
+
// Usage Example:
|
|
178
|
+
/*
|
|
179
|
+
import { TextInputWithValidation } from 'src/common/validations/examples';
|
|
180
|
+
|
|
181
|
+
const MyForm = () => {
|
|
182
|
+
const [email, setEmail] = useState('');
|
|
183
|
+
const [password, setPassword] = useState('');
|
|
184
|
+
const [phone, setPhone] = useState('');
|
|
185
|
+
const [customField, setCustomField] = useState('');
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<View>
|
|
189
|
+
<TextInputWithValidation
|
|
190
|
+
label="Email"
|
|
191
|
+
placeholder="Enter your email"
|
|
192
|
+
value={email}
|
|
193
|
+
onChangeText={setEmail}
|
|
194
|
+
validationType="email"
|
|
195
|
+
required
|
|
196
|
+
/>
|
|
197
|
+
|
|
198
|
+
<TextInputWithValidation
|
|
199
|
+
label="Password"
|
|
200
|
+
placeholder="Enter your password"
|
|
201
|
+
value={password}
|
|
202
|
+
onChangeText={setPassword}
|
|
203
|
+
validationType="password"
|
|
204
|
+
required
|
|
205
|
+
secureTextEntry
|
|
206
|
+
/>
|
|
207
|
+
|
|
208
|
+
<TextInputWithValidation
|
|
209
|
+
label="Phone Number"
|
|
210
|
+
placeholder="Enter your phone number"
|
|
211
|
+
value={phone}
|
|
212
|
+
onChangeText={setPhone}
|
|
213
|
+
validationType="phone"
|
|
214
|
+
/>
|
|
215
|
+
|
|
216
|
+
<TextInputWithValidation
|
|
217
|
+
label="Product ID"
|
|
218
|
+
placeholder="Enter product ID"
|
|
219
|
+
value={customField}
|
|
220
|
+
onChangeText={setCustomField}
|
|
221
|
+
validationType="custom"
|
|
222
|
+
customRegex={/^PRD-[0-9]{6}$/}
|
|
223
|
+
customErrorMessage="Product ID must be in format PRD-123456"
|
|
224
|
+
required
|
|
225
|
+
/>
|
|
226
|
+
</View>
|
|
227
|
+
);
|
|
228
|
+
};
|
|
229
|
+
*/
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validations Module
|
|
3
|
+
*
|
|
4
|
+
* Central export point for all validation utilities
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export * from './regexValidator';
|
|
8
|
+
|
|
9
|
+
// Additional regex patterns for specific use cases
|
|
10
|
+
export const REGEX_PATTERNS = {
|
|
11
|
+
// Standard patterns (exported also from regexValidator)
|
|
12
|
+
...require('./regexValidator').patterns,
|
|
13
|
+
|
|
14
|
+
// Additional patterns
|
|
15
|
+
STRONG_PASSWORD:
|
|
16
|
+
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
|
|
17
|
+
TIME_24H: /^([01]\d|2[0-3]):([0-5]\d)$/,
|
|
18
|
+
TIME_12H: /^(0?[1-9]|1[0-2]):([0-5]\d)\s?(AM|PM|am|pm)$/,
|
|
19
|
+
HEX_COLOR: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/,
|
|
20
|
+
POSTAL_CODE: /^[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d$|^\d{5}(-\d{4})?$/,
|
|
21
|
+
CURRENCY: /^[$€£¥]?([0-9]{1,3},([0-9]{3},)*[0-9]{3}|[0-9]+)(\.[0-9]{1,2})?$/,
|
|
22
|
+
MAC_ADDRESS: /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/,
|
|
23
|
+
|
|
24
|
+
// Form input validation
|
|
25
|
+
PRODUCT_SKU: /^[A-Za-z0-9-]{6,20}$/,
|
|
26
|
+
QUANTITY: /^[1-9]\d*$/,
|
|
27
|
+
DISCOUNT_PERCENTAGE: /^([0-9]|[1-9][0-9]|100)$/,
|
|
28
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regex Validation Constants
|
|
3
|
+
*
|
|
4
|
+
* A collection of regex patterns for common input validations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Email validation regex
|
|
8
|
+
export const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
9
|
+
|
|
10
|
+
// Phone number validation regex (international format)
|
|
11
|
+
export const PHONE_REGEX =
|
|
12
|
+
/^(\+\d{1,3}[-\s]?)?\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})$/;
|
|
13
|
+
|
|
14
|
+
// Password validation regex (min 8 chars, one uppercase, one lowercase, one number)
|
|
15
|
+
export const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
|
|
16
|
+
|
|
17
|
+
// Username validation regex (alphanumeric, 3-20 chars, underscores allowed)
|
|
18
|
+
export const USERNAME_REGEX = /^[a-zA-Z0-9_]{3,20}$/;
|
|
19
|
+
|
|
20
|
+
// Name validation regex (letters, spaces, apostrophes, hyphens)
|
|
21
|
+
export const NAME_REGEX = /^[a-zA-Z\s'-]{2,50}$/;
|
|
22
|
+
|
|
23
|
+
// URL validation regex
|
|
24
|
+
export const URL_REGEX =
|
|
25
|
+
/^(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)$/;
|
|
26
|
+
|
|
27
|
+
// Postal/ZIP code validation regex (US and Canada)
|
|
28
|
+
export const POSTAL_CODE_REGEX =
|
|
29
|
+
/^[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d$|^\d{5}(-\d{4})?$/;
|
|
30
|
+
|
|
31
|
+
// Credit card validation regex (major card types)
|
|
32
|
+
export const CREDIT_CARD_REGEX =
|
|
33
|
+
/^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|6(?:011|5[0-9][0-9])[0-9]{12})$/;
|
|
34
|
+
|
|
35
|
+
// Date validation regex (YYYY-MM-DD format)
|
|
36
|
+
export const DATE_REGEX = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
|
|
37
|
+
|
|
38
|
+
// IP Address validation regex
|
|
39
|
+
export const IP_ADDRESS_REGEX =
|
|
40
|
+
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
41
|
+
|
|
42
|
+
// Numeric value validation regex (integers only)
|
|
43
|
+
export const NUMERIC_REGEX = /^[0-9]+$/;
|
|
44
|
+
|
|
45
|
+
// Decimal value validation regex (positive or negative)
|
|
46
|
+
export const DECIMAL_REGEX = /^-?\d+(\.\d+)?$/;
|
|
47
|
+
|
|
48
|
+
// Price validation regex (positive decimal with up to 2 decimal places)
|
|
49
|
+
export const PRICE_REGEX = /^\d+(\.\d{1,2})?$/;
|
|
50
|
+
|
|
51
|
+
// Alphanumeric validation regex (letters and numbers only)
|
|
52
|
+
export const ALPHANUMERIC_REGEX = /^[a-zA-Z0-9]+$/;
|
|
53
|
+
|
|
54
|
+
// Letters only validation regex
|
|
55
|
+
export const LETTERS_ONLY_REGEX = /^[a-zA-Z]+$/;
|
|
56
|
+
|
|
57
|
+
// Strong password validation (8+ chars, upper, lower, number, special char)
|
|
58
|
+
export const STRONG_PASSWORD_REGEX =
|
|
59
|
+
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
|
|
60
|
+
|
|
61
|
+
// Time validation regex (HH:MM format, 24-hour)
|
|
62
|
+
export const TIME_24H_REGEX = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
|
63
|
+
|
|
64
|
+
// Time validation regex (HH:MM format, 12-hour with AM/PM)
|
|
65
|
+
export const TIME_12H_REGEX = /^(0?[1-9]|1[0-2]):([0-5]\d)\s?(AM|PM|am|pm)$/;
|
|
66
|
+
|
|
67
|
+
// Hex color code validation regex
|
|
68
|
+
export const HEX_COLOR_REGEX = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
|
|
69
|
+
|
|
70
|
+
// Social security number validation regex (US format)
|
|
71
|
+
export const SSN_REGEX =
|
|
72
|
+
/^(?!000|666|9\d{2})([0-8]\d{2})(?!00)(\d{2})(?!0000)(\d{4})$/;
|
|
73
|
+
|
|
74
|
+
// ISBN validation regex (ISBN-10 or ISBN-13)
|
|
75
|
+
export const ISBN_REGEX =
|
|
76
|
+
/^(?:ISBN(?:-1[03])?:?\s)?(?=[0-9X]{10}$|(?=(?:[0-9]+[-\s]){3})[-\s0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[-\s]){4})[-\s0-9]{17}$)(?:97[89][-\s]?)?[0-9]{1,5}[-\s]?[0-9]+[-\s]?[0-9]+[-\s]?[0-9X]$/;
|
|
77
|
+
|
|
78
|
+
// Currency validation regex (with symbol and thousands separators)
|
|
79
|
+
export const CURRENCY_REGEX =
|
|
80
|
+
/^[$€£¥]?([0-9]{1,3},([0-9]{3},)*[0-9]{3}|[0-9]+)(\.[0-9]{1,2})?$/;
|
|
81
|
+
|
|
82
|
+
// MAC Address validation regex
|
|
83
|
+
export const MAC_ADDRESS_REGEX = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regex Validation Utility
|
|
3
|
+
*
|
|
4
|
+
* TypeScript utility for regex-based validation with custom error messages.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Types for validation result
|
|
8
|
+
export interface ValidationResult {
|
|
9
|
+
isValid: boolean;
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Generic regex validation function
|
|
15
|
+
* @param value - The string value to validate
|
|
16
|
+
* @param regex - The regex pattern to validate against
|
|
17
|
+
* @param errorMessage - Optional custom error message
|
|
18
|
+
* @returns ValidationResult with isValid flag and message
|
|
19
|
+
*/
|
|
20
|
+
export const regexValidation = (
|
|
21
|
+
value: string,
|
|
22
|
+
regex: RegExp,
|
|
23
|
+
errorMessage: string = 'Invalid format',
|
|
24
|
+
): ValidationResult => {
|
|
25
|
+
const isValid = regex?.test(value);
|
|
26
|
+
return {
|
|
27
|
+
isValid,
|
|
28
|
+
message: isValid ? '' : errorMessage,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Validate if a field is empty
|
|
34
|
+
* @param value - The string value to check
|
|
35
|
+
* @param errorMessage - Optional custom error message
|
|
36
|
+
* @returns ValidationResult
|
|
37
|
+
*/
|
|
38
|
+
export const requiredValidation = (
|
|
39
|
+
value: string,
|
|
40
|
+
errorMessage: string = 'This field is required',
|
|
41
|
+
): ValidationResult => {
|
|
42
|
+
const isValid = value !== undefined && value !== null && value.trim() !== '';
|
|
43
|
+
return {
|
|
44
|
+
isValid,
|
|
45
|
+
message: isValid ? '' : errorMessage,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Validate minimum length
|
|
51
|
+
* @param value - The string value to check
|
|
52
|
+
* @param minLength - Minimum required length
|
|
53
|
+
* @param errorMessage - Optional custom error message
|
|
54
|
+
* @returns ValidationResult
|
|
55
|
+
*/
|
|
56
|
+
export const minLengthValidation = (
|
|
57
|
+
value: string,
|
|
58
|
+
minLength: number,
|
|
59
|
+
errorMessage?: string,
|
|
60
|
+
): ValidationResult => {
|
|
61
|
+
const isValid =
|
|
62
|
+
value !== undefined && value !== null && value.length >= minLength;
|
|
63
|
+
return {
|
|
64
|
+
isValid,
|
|
65
|
+
message: isValid
|
|
66
|
+
? ''
|
|
67
|
+
: errorMessage || `Input must be at least ${minLength} characters`,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Validate maximum length
|
|
73
|
+
* @param value - The string value to check
|
|
74
|
+
* @param maxLength - Maximum allowed length
|
|
75
|
+
* @param errorMessage - Optional custom error message
|
|
76
|
+
* @returns ValidationResult
|
|
77
|
+
*/
|
|
78
|
+
export const maxLengthValidation = (
|
|
79
|
+
value: string,
|
|
80
|
+
maxLength: number,
|
|
81
|
+
errorMessage?: string,
|
|
82
|
+
): ValidationResult => {
|
|
83
|
+
const isValid = !value || value.length <= maxLength;
|
|
84
|
+
return {
|
|
85
|
+
isValid,
|
|
86
|
+
message: isValid
|
|
87
|
+
? ''
|
|
88
|
+
: errorMessage || `Input cannot exceed ${maxLength} characters`,
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Run multiple validations in sequence
|
|
94
|
+
* @param value - The string value to validate
|
|
95
|
+
* @param validations - Array of validation functions to run
|
|
96
|
+
* @returns First failed ValidationResult or success result
|
|
97
|
+
*/
|
|
98
|
+
export const runValidations = (
|
|
99
|
+
value: string,
|
|
100
|
+
validations: ((value: string) => ValidationResult)[],
|
|
101
|
+
): ValidationResult => {
|
|
102
|
+
for (const validationFn of validations) {
|
|
103
|
+
const result = validationFn(value);
|
|
104
|
+
if (!result.isValid) {
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {isValid: true, message: ''};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Common validation patterns for easy access
|
|
113
|
+
*/
|
|
114
|
+
export const patterns = {
|
|
115
|
+
// Email validation regex
|
|
116
|
+
EMAIL: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
|
|
117
|
+
|
|
118
|
+
// Phone number validation regex (international format)
|
|
119
|
+
PHONE: /^(\+\d{1,3}[-\s]?)?\(?(\d{3})\)?[-.\s]?(\d{3})[-.\s]?(\d{4})$/,
|
|
120
|
+
|
|
121
|
+
// Password validation regex (min 8 chars, one uppercase, one lowercase, one number)
|
|
122
|
+
PASSWORD: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/,
|
|
123
|
+
|
|
124
|
+
// Username validation regex (alphanumeric, 3-20 chars, underscores allowed)
|
|
125
|
+
USERNAME: /^[a-zA-Z0-9_]{3,20}$/,
|
|
126
|
+
|
|
127
|
+
// Name validation regex (letters, spaces, apostrophes, hyphens)
|
|
128
|
+
NAME: /^[a-zA-Z\s'-]{2,50}$/,
|
|
129
|
+
|
|
130
|
+
// URL validation regex
|
|
131
|
+
URL: /^(https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)$/,
|
|
132
|
+
|
|
133
|
+
// Numeric value validation regex (integers only)
|
|
134
|
+
NUMERIC: /^[0-9]+$/,
|
|
135
|
+
|
|
136
|
+
// Decimal value validation regex (positive or negative)
|
|
137
|
+
DECIMAL: /^-?\d+(\.\d+)?$/,
|
|
138
|
+
|
|
139
|
+
// Price validation regex (positive decimal with up to 2 decimal places)
|
|
140
|
+
PRICE: /^\d+(\.\d{1,2})?$/,
|
|
141
|
+
|
|
142
|
+
// Alphanumeric validation regex (letters and numbers only)
|
|
143
|
+
ALPHANUMERIC: /^[a-zA-Z0-9]+$/,
|
|
144
|
+
|
|
145
|
+
// Letters only validation regex
|
|
146
|
+
LETTERS_ONLY: /^[a-zA-Z]+$/,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Export convenience validation functions
|
|
150
|
+
export const validateEmail = (
|
|
151
|
+
email: string,
|
|
152
|
+
errorMessage?: string,
|
|
153
|
+
): ValidationResult =>
|
|
154
|
+
regexValidation(
|
|
155
|
+
email,
|
|
156
|
+
patterns.EMAIL,
|
|
157
|
+
errorMessage || 'Please enter a valid email address',
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
export const validatePhone = (
|
|
161
|
+
phone: string,
|
|
162
|
+
errorMessage?: string,
|
|
163
|
+
): ValidationResult =>
|
|
164
|
+
regexValidation(
|
|
165
|
+
phone,
|
|
166
|
+
patterns.PHONE,
|
|
167
|
+
errorMessage || 'Please enter a valid phone number',
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
export const validatePassword = (
|
|
171
|
+
password: string,
|
|
172
|
+
errorMessage?: string,
|
|
173
|
+
): ValidationResult =>
|
|
174
|
+
regexValidation(
|
|
175
|
+
password,
|
|
176
|
+
patterns.PASSWORD,
|
|
177
|
+
errorMessage ||
|
|
178
|
+
'Password must be at least 8 characters with uppercase, lowercase, and number',
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
export const validateUsername = (
|
|
182
|
+
username: string,
|
|
183
|
+
errorMessage?: string,
|
|
184
|
+
): ValidationResult =>
|
|
185
|
+
regexValidation(
|
|
186
|
+
username,
|
|
187
|
+
patterns.USERNAME,
|
|
188
|
+
errorMessage ||
|
|
189
|
+
'Username must be 3-20 characters (letters, numbers, underscores)',
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
export const validateName = (
|
|
193
|
+
name: string,
|
|
194
|
+
errorMessage?: string,
|
|
195
|
+
): ValidationResult =>
|
|
196
|
+
regexValidation(
|
|
197
|
+
name,
|
|
198
|
+
patterns.NAME,
|
|
199
|
+
errorMessage || 'Please enter a valid name',
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
export const validateUrl = (
|
|
203
|
+
url: string,
|
|
204
|
+
errorMessage?: string,
|
|
205
|
+
): ValidationResult =>
|
|
206
|
+
regexValidation(
|
|
207
|
+
url,
|
|
208
|
+
patterns.URL,
|
|
209
|
+
errorMessage || 'Please enter a valid URL',
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
export const validateNumeric = (
|
|
213
|
+
value: string,
|
|
214
|
+
errorMessage?: string,
|
|
215
|
+
): ValidationResult =>
|
|
216
|
+
regexValidation(
|
|
217
|
+
value,
|
|
218
|
+
patterns.NUMERIC,
|
|
219
|
+
errorMessage || 'Please enter numbers only',
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
export const validateDecimal = (
|
|
223
|
+
value: string,
|
|
224
|
+
errorMessage?: string,
|
|
225
|
+
): ValidationResult =>
|
|
226
|
+
regexValidation(
|
|
227
|
+
value,
|
|
228
|
+
patterns.DECIMAL,
|
|
229
|
+
errorMessage || 'Please enter a valid decimal number',
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
export const validatePrice = (
|
|
233
|
+
price: string,
|
|
234
|
+
errorMessage?: string,
|
|
235
|
+
): ValidationResult =>
|
|
236
|
+
regexValidation(
|
|
237
|
+
price,
|
|
238
|
+
patterns.PRICE,
|
|
239
|
+
errorMessage || 'Please enter a valid price (e.g., 10.99)',
|
|
240
|
+
);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts meaningful error information from Axios error responses
|
|
3
|
+
* and ensures the message is always a string
|
|
4
|
+
* @param error The error caught from an API request
|
|
5
|
+
* @returns A structured error object with message (as string) and details
|
|
6
|
+
*/
|
|
7
|
+
import {ensureString} from '../utils/stringUtils';
|
|
8
|
+
|
|
9
|
+
export const extractServerError = (error: any) => {
|
|
10
|
+
let errorObj = {
|
|
11
|
+
message: 'Unknown error occurred',
|
|
12
|
+
status: 500,
|
|
13
|
+
originalError: error,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
// Extract error information from Axios response
|
|
18
|
+
if (error?.response?.data) {
|
|
19
|
+
const data = error.response.data;
|
|
20
|
+
|
|
21
|
+
// Handle different error formats
|
|
22
|
+
if (data.error) {
|
|
23
|
+
errorObj.message = ensureString(data.error);
|
|
24
|
+
} else {
|
|
25
|
+
errorObj.message = ensureString(data);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
errorObj.status = error.response.status;
|
|
29
|
+
errorObj.originalError = data;
|
|
30
|
+
} else if (error?.message) {
|
|
31
|
+
errorObj.message = ensureString(error.message);
|
|
32
|
+
}
|
|
33
|
+
} catch (e) {}
|
|
34
|
+
|
|
35
|
+
// Final safety check
|
|
36
|
+
errorObj.message = ensureString(errorObj.message);
|
|
37
|
+
|
|
38
|
+
return errorObj;
|
|
39
|
+
};
|
|
@@ -1,35 +1,12 @@
|
|
|
1
1
|
import {AxiosResponse} from 'axios';
|
|
2
2
|
import Snackbar from 'react-native-snackbar';
|
|
3
|
-
import {Colors} from '../theme/colors';
|
|
4
3
|
import {setLogout} from '../store/user/userSlice';
|
|
5
4
|
import {store} from '../store/store';
|
|
6
5
|
|
|
7
|
-
export const handleFetchJsonResponse =
|
|
6
|
+
export const handleFetchJsonResponse = (
|
|
8
7
|
response: AxiosResponse,
|
|
9
8
|
showSuccessMessage?: boolean,
|
|
10
9
|
) => {
|
|
11
|
-
if (response.status === 401) {
|
|
12
|
-
store.dispatch(setLogout());
|
|
13
|
-
} else if (
|
|
14
|
-
!response.status ||
|
|
15
|
-
response.status < 200 ||
|
|
16
|
-
(response.status >= 300 && response.status !== 401)
|
|
17
|
-
) {
|
|
18
|
-
return Snackbar.show({
|
|
19
|
-
text: response?.data?.message,
|
|
20
|
-
duration: Snackbar.LENGTH_SHORT,
|
|
21
|
-
textColor: Colors.white,
|
|
22
|
-
backgroundColor: Colors.red,
|
|
23
|
-
});
|
|
24
|
-
} else if (showSuccessMessage) {
|
|
25
|
-
Snackbar.show({
|
|
26
|
-
text: 'Success',
|
|
27
|
-
duration: Snackbar.LENGTH_SHORT,
|
|
28
|
-
textColor: Colors.white,
|
|
29
|
-
backgroundColor: Colors.green,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
10
|
return response.data;
|
|
34
11
|
};
|
|
35
12
|
|
|
@@ -37,7 +14,5 @@ export const handleErrorResponse = async (message: string) => {
|
|
|
37
14
|
return Snackbar.show({
|
|
38
15
|
text: message,
|
|
39
16
|
duration: Snackbar.LENGTH_SHORT,
|
|
40
|
-
textColor: Colors.white,
|
|
41
|
-
backgroundColor: Colors.red,
|
|
42
17
|
});
|
|
43
18
|
};
|