@umituz/react-native-settings 4.23.86 → 4.23.87

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 (28) hide show
  1. package/package.json +1 -1
  2. package/src/domains/feedback/presentation/components/FeedbackForm.tsx +10 -3
  3. package/src/domains/gamification/store/gamificationStore.ts +6 -7
  4. package/src/domains/localization/infrastructure/storage/LocalizationStore.ts +50 -181
  5. package/src/domains/localization/infrastructure/storage/localizationStoreUtils.ts +182 -0
  6. package/src/domains/notifications/reminders/presentation/components/ReminderForm.tsx +51 -45
  7. package/src/infrastructure/types/commonComponentTypes.ts +142 -0
  8. package/src/infrastructure/utils/async/core.ts +109 -0
  9. package/src/infrastructure/utils/async/debounceAndBatch.ts +69 -0
  10. package/src/infrastructure/utils/async/index.ts +8 -0
  11. package/src/infrastructure/utils/async/retryAndTimeout.ts +57 -0
  12. package/src/infrastructure/utils/configFactory.ts +101 -0
  13. package/src/infrastructure/utils/errorHandlers.ts +249 -0
  14. package/src/infrastructure/utils/index.ts +5 -0
  15. package/src/infrastructure/utils/memoUtils.ts +10 -2
  16. package/src/infrastructure/utils/styleTokens.ts +132 -0
  17. package/src/infrastructure/utils/validation/core.ts +42 -0
  18. package/src/infrastructure/utils/validation/formValidators.ts +82 -0
  19. package/src/infrastructure/utils/validation/index.ts +37 -0
  20. package/src/infrastructure/utils/validation/numericValidators.ts +66 -0
  21. package/src/infrastructure/utils/validation/passwordValidator.ts +53 -0
  22. package/src/infrastructure/utils/validation/textValidators.ts +118 -0
  23. package/src/presentation/hooks/useSettingsScreenConfig.ts +32 -79
  24. package/src/presentation/utils/config-creators/base-configs.ts +54 -42
  25. package/src/presentation/utils/faqTranslator.ts +31 -0
  26. package/src/presentation/utils/index.ts +6 -1
  27. package/src/presentation/utils/settingsConfigFactory.ts +89 -0
  28. package/src/presentation/utils/useAuthHandlers.ts +98 -0
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Error Handling Utilities
3
+ * Centralized error handling and error message generation
4
+ * FIXED: Added safety checks for showToast and proper error handling
5
+ */
6
+
7
+ /**
8
+ * Error types for better error classification
9
+ */
10
+ export enum ErrorType {
11
+ NETWORK = "NETWORK",
12
+ VALIDATION = "VALIDATION",
13
+ AUTHENTICATION = "AUTHENTICATION",
14
+ AUTHORIZATION = "AUTHORIZATION",
15
+ NOT_FOUND = "NOT_FOUND",
16
+ SERVER = "SERVER",
17
+ UNKNOWN = "UNKNOWN",
18
+ }
19
+
20
+ /**
21
+ * Custom error class with error type
22
+ */
23
+ export class AppError extends Error {
24
+ constructor(
25
+ message: string,
26
+ public type: ErrorType = ErrorType.UNKNOWN,
27
+ public code?: string,
28
+ public statusCode?: number
29
+ ) {
30
+ super(message);
31
+ this.name = "AppError";
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Error handler result
37
+ */
38
+ export interface ErrorHandlerResult {
39
+ message: string;
40
+ type: ErrorType;
41
+ shouldShowToUser: boolean;
42
+ }
43
+
44
+ /**
45
+ * Classify error based on error properties
46
+ */
47
+ export const classifyError = (error: unknown): ErrorType => {
48
+ if (error instanceof AppError) {
49
+ return error.type;
50
+ }
51
+
52
+ if (!(error instanceof Error)) {
53
+ return ErrorType.UNKNOWN;
54
+ }
55
+
56
+ const errorMessage = error.message.toLowerCase();
57
+
58
+ // Network errors
59
+ if (
60
+ errorMessage.includes("network") ||
61
+ errorMessage.includes("fetch") ||
62
+ errorMessage.includes("connection")
63
+ ) {
64
+ return ErrorType.NETWORK;
65
+ }
66
+
67
+ // Authentication errors
68
+ if (
69
+ errorMessage.includes("unauthorized") ||
70
+ errorMessage.includes("unauthenticated") ||
71
+ errorMessage.includes("token") ||
72
+ errorMessage.includes("401")
73
+ ) {
74
+ return ErrorType.AUTHENTICATION;
75
+ }
76
+
77
+ // Authorization errors
78
+ if (
79
+ errorMessage.includes("forbidden") ||
80
+ errorMessage.includes("permission") ||
81
+ errorMessage.includes("403")
82
+ ) {
83
+ return ErrorType.AUTHORIZATION;
84
+ }
85
+
86
+ // Not found errors
87
+ if (
88
+ errorMessage.includes("not found") ||
89
+ errorMessage.includes("404")
90
+ ) {
91
+ return ErrorType.NOT_FOUND;
92
+ }
93
+
94
+ // Server errors
95
+ if (
96
+ errorMessage.includes("500") ||
97
+ errorMessage.includes("502") ||
98
+ errorMessage.includes("503")
99
+ ) {
100
+ return ErrorType.SERVER;
101
+ }
102
+
103
+ return ErrorType.UNKNOWN;
104
+ };
105
+
106
+ /**
107
+ * Get user-friendly error message
108
+ */
109
+ export const getUserFriendlyErrorMessage = (
110
+ error: unknown,
111
+ context?: {
112
+ operation?: string;
113
+ entity?: string;
114
+ }
115
+ ): ErrorHandlerResult => {
116
+ const errorType = classifyError(error);
117
+
118
+ let message = "An unexpected error occurred";
119
+ let shouldShowToUser = true;
120
+
121
+ switch (errorType) {
122
+ case ErrorType.NETWORK:
123
+ message = "Unable to connect. Please check your internet connection and try again.";
124
+ break;
125
+
126
+ case ErrorType.AUTHENTICATION:
127
+ message = "Your session has expired. Please sign in again.";
128
+ break;
129
+
130
+ case ErrorType.AUTHORIZATION:
131
+ message = "You don't have permission to perform this action.";
132
+ break;
133
+
134
+ case ErrorType.NOT_FOUND:
135
+ if (context?.entity) {
136
+ // FIXED: Sanitize entity name to prevent injection
137
+ const sanitizedEntity = String(context.entity).replace(/[<>]/g, "");
138
+ message = `${sanitizedEntity} not found`;
139
+ } else {
140
+ message = "The requested resource was not found.";
141
+ }
142
+ break;
143
+
144
+ case ErrorType.SERVER:
145
+ message = "Server error. Please try again later.";
146
+ shouldShowToUser = false;
147
+ break;
148
+
149
+ case ErrorType.VALIDATION:
150
+ message = error instanceof Error ? error.message : "Invalid input";
151
+ break;
152
+
153
+ default:
154
+ if (error instanceof Error) {
155
+ message = error.message;
156
+ }
157
+ }
158
+
159
+ return { message, type: errorType, shouldShowToUser };
160
+ };
161
+
162
+ /**
163
+ * Log error appropriately based on type
164
+ */
165
+ export const logError = (
166
+ error: unknown,
167
+ context?: {
168
+ operation?: string;
169
+ userId?: string;
170
+ additionalInfo?: Record<string, unknown>;
171
+ }
172
+ ): void => {
173
+ const errorType = classifyError(error);
174
+ const errorMessage = error instanceof Error ? error.message : String(error);
175
+
176
+ const logData = {
177
+ type: errorType,
178
+ message: errorMessage,
179
+ operation: context?.operation,
180
+ userId: context?.userId,
181
+ ...context?.additionalInfo,
182
+ };
183
+
184
+ // In production, send to error tracking service
185
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
186
+ console.error("[Error]", logData);
187
+ }
188
+
189
+ // TODO: Send to error tracking service in production
190
+ // ErrorTracking.captureException(error, logData);
191
+ };
192
+
193
+ /**
194
+ * Handle error with logging and user message
195
+ */
196
+ export const handleError = (
197
+ error: unknown,
198
+ context?: {
199
+ operation?: string;
200
+ entity?: string;
201
+ userId?: string;
202
+ showToast?: (message: string) => void;
203
+ }
204
+ ): ErrorHandlerResult => {
205
+ // Log the error
206
+ logError(error, context);
207
+
208
+ // Get user-friendly message
209
+ const result = getUserFriendlyErrorMessage(error, context);
210
+
211
+ // Show toast if provided and safe to do so
212
+ // FIXED: Added safety check and try-catch for showToast
213
+ if (context?.showToast && result.shouldShowToUser) {
214
+ try {
215
+ if (typeof context.showToast === "function") {
216
+ context.showToast(result.message);
217
+ }
218
+ } catch (toastError) {
219
+ // Log toast error but don't crash error handling
220
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
221
+ console.error("[ErrorHandlers] showToast failed:", toastError);
222
+ }
223
+ }
224
+ }
225
+
226
+ return result;
227
+ };
228
+
229
+ /**
230
+ * Wrap an async function with error handling
231
+ */
232
+ export const withErrorHandling = async <T>(
233
+ operation: () => Promise<T>,
234
+ options: {
235
+ operation?: string;
236
+ entity?: string;
237
+ showToast?: (message: string) => void;
238
+ onError?: (result: ErrorHandlerResult) => void;
239
+ }
240
+ ): Promise<{ success: true; data: T } | { success: false; error: ErrorHandlerResult }> => {
241
+ try {
242
+ const data = await operation();
243
+ return { success: true, data };
244
+ } catch (error) {
245
+ const errorResult = handleError(error, options);
246
+ options?.onError?.(errorResult);
247
+ return { success: false, error: errorResult };
248
+ }
249
+ };
@@ -5,3 +5,8 @@
5
5
 
6
6
  export * from './styleUtils';
7
7
  export * from './memoUtils';
8
+ export * from './styleTokens';
9
+ export * from './configFactory';
10
+ export * from './validation';
11
+ export * from './async';
12
+ export * from './errorHandlers';
@@ -2,7 +2,7 @@
2
2
  * Memo Utilities
3
3
  * Centralized memoization helpers to reduce code duplication
4
4
  */
5
- import { useMemo, useCallback, useRef, DependencyList } from 'react';
5
+ import { useMemo, useCallback, useRef, useEffect, DependencyList } from 'react';
6
6
  import type { DesignTokens } from '@umituz/react-native-design-system';
7
7
 
8
8
  /**
@@ -114,7 +114,7 @@ export function useMemoWithEquality<T>(
114
114
  }
115
115
 
116
116
  /**
117
- * Custom hook that creates a debounced callback
117
+ * Custom hook that creates a debounced callback with proper cleanup
118
118
  * @param callback Function to debounce
119
119
  * @param delay Delay in milliseconds
120
120
  * @returns Debounced callback
@@ -125,6 +125,14 @@ export function useDebouncedCallback<T extends (...args: any[]) => any>(
125
125
  ): T {
126
126
  const timeoutRef = useRef<NodeJS.Timeout | null>(null);
127
127
 
128
+ useEffect(() => {
129
+ return () => {
130
+ if (timeoutRef.current) {
131
+ clearTimeout(timeoutRef.current);
132
+ }
133
+ };
134
+ }, []);
135
+
128
136
  return useCallback((...args: Parameters<T>) => {
129
137
  if (timeoutRef.current) {
130
138
  clearTimeout(timeoutRef.current);
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Style Tokens
3
+ * Centralized design tokens to replace magic numbers across the codebase
4
+ */
5
+
6
+ /**
7
+ * Spacing tokens - replaces magic numbers for padding, margins, gaps
8
+ */
9
+ export const SPACING = {
10
+ xs: 4,
11
+ sm: 8,
12
+ md: 12,
13
+ lg: 16,
14
+ xl: 20,
15
+ xxl: 24,
16
+ xxxl: 32,
17
+ } as const;
18
+
19
+ /**
20
+ * Font size tokens - replaces magic numbers for font sizes
21
+ */
22
+ export const FONT_SIZE = {
23
+ xs: 10,
24
+ sm: 12,
25
+ md: 14,
26
+ base: 16,
27
+ lg: 18,
28
+ xl: 20,
29
+ xxl: 24,
30
+ xxxl: 28,
31
+ display: 32,
32
+ } as const;
33
+
34
+ /**
35
+ * Border radius tokens - replaces magic numbers for border radius
36
+ */
37
+ export const BORDER_RADIUS = {
38
+ none: 0,
39
+ sm: 4,
40
+ md: 8,
41
+ lg: 12,
42
+ xl: 16,
43
+ full: 9999,
44
+ } as const;
45
+
46
+ /**
47
+ * Opacity tokens - replaces magic numbers for opacity values
48
+ */
49
+ export const OPACITY = {
50
+ disabled: 0.3,
51
+ hover: 0.7,
52
+ focus: 0.8,
53
+ pressed: 0.9,
54
+ overlay: 0.5,
55
+ icon: 0.6,
56
+ } as const;
57
+
58
+ /**
59
+ * Icon size tokens
60
+ */
61
+ export const ICON_SIZE = {
62
+ xs: 16,
63
+ sm: 20,
64
+ md: 24,
65
+ lg: 32,
66
+ xl: 40,
67
+ xxl: 48,
68
+ } as const;
69
+
70
+ /**
71
+ * Layout dimension tokens
72
+ */
73
+ export const DIMENSION = {
74
+ touchTarget: {
75
+ minHeight: 44,
76
+ minWidth: 44,
77
+ },
78
+ card: {
79
+ minHeight: 72,
80
+ },
81
+ input: {
82
+ minHeight: 48,
83
+ },
84
+ button: {
85
+ minHeight: 44,
86
+ height: 48,
87
+ },
88
+ avatar: {
89
+ xs: 24,
90
+ sm: 32,
91
+ md: 40,
92
+ lg: 48,
93
+ xl: 64,
94
+ xxl: 96,
95
+ },
96
+ } as const;
97
+
98
+ /**
99
+ * Z-index tokens for layering
100
+ */
101
+ export const Z_INDEX = {
102
+ base: 0,
103
+ overlay: 10,
104
+ dropdown: 100,
105
+ sticky: 200,
106
+ fixed: 300,
107
+ modalBackdrop: 400,
108
+ modal: 500,
109
+ popover: 600,
110
+ toast: 700,
111
+ } as const;
112
+
113
+ /**
114
+ * Animation duration tokens (in ms)
115
+ */
116
+ export const DURATION = {
117
+ fast: 150,
118
+ normal: 250,
119
+ slow: 350,
120
+ extraSlow: 500,
121
+ } as const;
122
+
123
+ /**
124
+ * Type exports for token values
125
+ */
126
+ export type SpacingToken = typeof SPACING[keyof typeof SPACING];
127
+ export type FontSizeToken = typeof FONT_SIZE[keyof typeof FONT_SIZE];
128
+ export type BorderRadiusToken = typeof BORDER_RADIUS[keyof typeof BORDER_RADIUS];
129
+ export type OpacityToken = typeof OPACITY[keyof typeof OPACITY];
130
+ export type IconSizeToken = typeof ICON_SIZE[keyof typeof ICON_SIZE];
131
+ export type ZIndexToken = typeof Z_INDEX[keyof typeof Z_INDEX];
132
+ export type DurationToken = typeof DURATION[keyof typeof DURATION];
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Core Validation Utilities
3
+ * Base validation interfaces and types
4
+ */
5
+
6
+ /**
7
+ * Validation result interface
8
+ */
9
+ export interface ValidationResult {
10
+ isValid: boolean;
11
+ error?: string;
12
+ }
13
+
14
+ /**
15
+ * Text validation options
16
+ */
17
+ export interface TextValidationOptions {
18
+ minLength?: number;
19
+ maxLength?: number;
20
+ required?: boolean;
21
+ pattern?: RegExp;
22
+ customValidator?: (value: string) => string | null;
23
+ }
24
+
25
+ /**
26
+ * Email validation options
27
+ */
28
+ export interface EmailValidationOptions {
29
+ required?: boolean;
30
+ allowEmpty?: boolean;
31
+ }
32
+
33
+ /**
34
+ * Password validation options
35
+ */
36
+ export interface PasswordValidationOptions {
37
+ minLength?: number;
38
+ requireUppercase?: boolean;
39
+ requireLowercase?: boolean;
40
+ requireNumber?: boolean;
41
+ requireSpecialChar?: boolean;
42
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Form Validators
3
+ * Domain-specific form validation functions
4
+ */
5
+
6
+ import type { ValidationResult } from "./core";
7
+ import { validateRating } from "./numericValidators";
8
+
9
+ /**
10
+ * Feedback form validation
11
+ */
12
+ export const validateFeedbackForm = (data: {
13
+ type: string;
14
+ rating: number;
15
+ description: string;
16
+ }): ValidationResult => {
17
+ // Validate rating
18
+ const ratingResult = validateRating(data.rating);
19
+ if (!ratingResult.isValid) {
20
+ return ratingResult;
21
+ }
22
+
23
+ // Validate description (required)
24
+ if (!data.description.trim()) {
25
+ return { isValid: false, error: "Please provide a description" };
26
+ }
27
+
28
+ // Check description length
29
+ if (data.description.length < 10) {
30
+ return { isValid: false, error: "Description must be at least 10 characters" };
31
+ }
32
+
33
+ if (data.description.length > 1000) {
34
+ return { isValid: false, error: "Description must be less than 1000 characters" };
35
+ }
36
+
37
+ return { isValid: true };
38
+ };
39
+
40
+ /**
41
+ * Reminder form validation
42
+ */
43
+ export const validateReminderForm = (data: {
44
+ title: string;
45
+ body?: string;
46
+ frequency: string;
47
+ hour?: number;
48
+ minute?: number;
49
+ weekday?: number;
50
+ maxTitleLength?: number;
51
+ maxBodyLength?: number;
52
+ }): ValidationResult => {
53
+ // Validate title
54
+ if (!data.title.trim()) {
55
+ return { isValid: false, error: "Title is required" };
56
+ }
57
+
58
+ if (data.maxTitleLength && data.title.length > data.maxTitleLength) {
59
+ return { isValid: false, error: `Title must be less than ${data.maxTitleLength} characters` };
60
+ }
61
+
62
+ // Validate body length if provided
63
+ if (data.body && data.maxBodyLength && data.body.length > data.maxBodyLength) {
64
+ return { isValid: false, error: `Body must be less than ${data.maxBodyLength} characters` };
65
+ }
66
+
67
+ // Validate time values if provided
68
+ if (data.hour !== undefined && (data.hour < 0 || data.hour > 23)) {
69
+ return { isValid: false, error: "Hour must be between 0 and 23" };
70
+ }
71
+
72
+ if (data.minute !== undefined && (data.minute < 0 || data.minute > 59)) {
73
+ return { isValid: false, error: "Minute must be between 0 and 59" };
74
+ }
75
+
76
+ // Validate weekday if frequency is weekly
77
+ if (data.frequency === "weekly" && (data.weekday === undefined || data.weekday < 0 || data.weekday > 6)) {
78
+ return { isValid: false, error: "Please select a valid day" };
79
+ }
80
+
81
+ return { isValid: true };
82
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Validation Utilities
3
+ * Barrel export for all validation modules
4
+ */
5
+
6
+ export * from "./core";
7
+ export * from "./textValidators";
8
+ export * from "./passwordValidator";
9
+ export * from "./numericValidators";
10
+ export * from "./formValidators";
11
+
12
+ import type { ValidationResult } from "./core";
13
+
14
+ /**
15
+ * Form field validator factory
16
+ * Creates a validator function for a specific field
17
+ */
18
+ export const createFieldValidator = <T>(
19
+ validator: (value: T) => ValidationResult
20
+ ) => {
21
+ return (value: T): ValidationResult => validator(value);
22
+ };
23
+
24
+ /**
25
+ * Multi-field validator
26
+ * Validates multiple fields and returns the first error
27
+ */
28
+ export const validateMultipleFields = (
29
+ fields: Record<string, ValidationResult>
30
+ ): ValidationResult => {
31
+ for (const result of Object.values(fields)) {
32
+ if (!result.isValid) {
33
+ return result;
34
+ }
35
+ }
36
+ return { isValid: true };
37
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Numeric Validators
3
+ * Validation functions for numeric inputs
4
+ */
5
+
6
+ import type { ValidationResult } from "./core";
7
+
8
+ /**
9
+ * Validates a rating value (1-5)
10
+ */
11
+ export const validateRating = (rating: number): ValidationResult => {
12
+ if (rating < 1 || rating > 5) {
13
+ return { isValid: false, error: "Rating must be between 1 and 5" };
14
+ }
15
+ return { isValid: true };
16
+ };
17
+
18
+ /**
19
+ * Validates a number is within a range
20
+ */
21
+ export const validateRange = (
22
+ value: number,
23
+ min: number,
24
+ max: number,
25
+ fieldName?: string
26
+ ): ValidationResult => {
27
+ if (value < min || value > max) {
28
+ return {
29
+ isValid: false,
30
+ error: `${fieldName || "Value"} must be between ${min} and ${max}`,
31
+ };
32
+ }
33
+ return { isValid: true };
34
+ };
35
+
36
+ /**
37
+ * Validates a positive number
38
+ */
39
+ export const validatePositiveNumber = (
40
+ value: number,
41
+ fieldName?: string
42
+ ): ValidationResult => {
43
+ if (value <= 0) {
44
+ return {
45
+ isValid: false,
46
+ error: `${fieldName || "Value"} must be greater than 0`,
47
+ };
48
+ }
49
+ return { isValid: true };
50
+ };
51
+
52
+ /**
53
+ * Validates a non-negative number
54
+ */
55
+ export const validateNonNegativeNumber = (
56
+ value: number,
57
+ fieldName?: string
58
+ ): ValidationResult => {
59
+ if (value < 0) {
60
+ return {
61
+ isValid: false,
62
+ error: `${fieldName || "Value"} must be 0 or greater`,
63
+ };
64
+ }
65
+ return { isValid: true };
66
+ };
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Password Validator
3
+ * Password-specific validation logic
4
+ */
5
+
6
+ import type { ValidationResult, PasswordValidationOptions } from "./core";
7
+
8
+ /**
9
+ * Validates a password
10
+ */
11
+ export const validatePassword = (
12
+ password: string,
13
+ options: PasswordValidationOptions = {}
14
+ ): ValidationResult => {
15
+ const {
16
+ minLength = 8,
17
+ requireUppercase = false,
18
+ requireLowercase = false,
19
+ requireNumber = false,
20
+ requireSpecialChar = false,
21
+ } = options;
22
+
23
+ const errors: string[] = [];
24
+
25
+ if (password.length < minLength) {
26
+ errors.push(`at least ${minLength} characters`);
27
+ }
28
+
29
+ if (requireUppercase && !/[A-Z]/.test(password)) {
30
+ errors.push("one uppercase letter");
31
+ }
32
+
33
+ if (requireLowercase && !/[a-z]/.test(password)) {
34
+ errors.push("one lowercase letter");
35
+ }
36
+
37
+ if (requireNumber && !/\d/.test(password)) {
38
+ errors.push("one number");
39
+ }
40
+
41
+ if (requireSpecialChar && !/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]/.test(password)) {
42
+ errors.push("one special character");
43
+ }
44
+
45
+ if (errors.length > 0) {
46
+ return {
47
+ isValid: false,
48
+ error: `Password must contain ${errors.join(", ")}`,
49
+ };
50
+ }
51
+
52
+ return { isValid: true };
53
+ };