@umituz/react-native-design-system 4.23.117 → 4.23.118

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 (31) hide show
  1. package/package.json +1 -1
  2. package/src/device/presentation/hooks/useAnonymousUser.ts +25 -31
  3. package/src/device/presentation/hooks/useDeviceInfo.ts +47 -91
  4. package/src/init/useAppInitialization.ts +24 -57
  5. package/src/media/domain/entities/Media.ts +1 -0
  6. package/src/media/infrastructure/utils/media-collection-utils.ts +9 -6
  7. package/src/media/presentation/hooks/useMedia.ts +73 -128
  8. package/src/molecules/alerts/AlertBanner.tsx +24 -46
  9. package/src/molecules/alerts/AlertInline.tsx +8 -9
  10. package/src/molecules/alerts/AlertModal.tsx +23 -14
  11. package/src/molecules/alerts/AlertToast.tsx +25 -53
  12. package/src/molecules/alerts/components/AlertContent.tsx +79 -0
  13. package/src/molecules/alerts/components/AlertIcon.tsx +31 -0
  14. package/src/molecules/alerts/components/index.ts +6 -0
  15. package/src/molecules/alerts/hooks/index.ts +6 -0
  16. package/src/molecules/alerts/hooks/useAlertAutoDismiss.ts +26 -0
  17. package/src/molecules/alerts/hooks/useAlertDismissHandler.ts +21 -0
  18. package/src/molecules/alerts/utils/alertUtils.ts +0 -21
  19. package/src/storage/cache/presentation/useCachedValue.ts +24 -65
  20. package/src/storage/presentation/hooks/useStorageState.ts +20 -29
  21. package/src/utilities/sharing/presentation/hooks/useSharing.ts +75 -140
  22. package/src/utils/errors/DesignSystemError.ts +57 -1
  23. package/src/utils/errors/ErrorHandler.ts +105 -1
  24. package/src/utils/errors/adapters/CacheErrorAdapter.ts +68 -0
  25. package/src/utils/errors/adapters/ImageErrorAdapter.ts +91 -0
  26. package/src/utils/errors/adapters/StorageErrorAdapter.ts +107 -0
  27. package/src/utils/errors/index.ts +5 -1
  28. package/src/utils/errors/types/Result.ts +64 -0
  29. package/src/utils/hooks/index.ts +12 -0
  30. package/src/utils/hooks/types/AsyncOperationTypes.ts +75 -0
  31. package/src/utils/hooks/useAsyncOperation.ts +223 -0
@@ -2,9 +2,10 @@
2
2
  * useCachedValue Hook
3
3
  */
4
4
 
5
- import { useCallback, useEffect, useRef, useState } from 'react';
5
+ import { useCallback, useRef, useMemo } from 'react';
6
6
  import { cacheManager } from '../domain/CacheManager';
7
7
  import type { CacheConfig } from '../domain/types/Cache';
8
+ import { useAsyncOperation } from '../../../utils/hooks';
8
9
 
9
10
  export function useCachedValue<T>(
10
11
  cacheName: string,
@@ -12,95 +13,53 @@ export function useCachedValue<T>(
12
13
  fetcher: () => Promise<T>,
13
14
  config?: CacheConfig & { ttl?: number }
14
15
  ) {
15
- const [value, setValue] = useState<T | undefined>(undefined);
16
- const [isLoading, setIsLoading] = useState(false);
17
- const [error, setError] = useState<Error | null>(null);
18
-
19
16
  const fetcherRef = useRef(fetcher);
20
17
  const configRef = useRef(config);
21
18
 
22
- const loadValue = useCallback(async () => {
23
- const cache = cacheManager.getCache<T>(cacheName, configRef.current);
24
- const cached = cache.get(key);
25
-
26
- if (cached !== undefined) {
27
- setValue(cached);
28
- return;
29
- }
30
-
31
- setIsLoading(true);
32
- setError(null);
33
-
34
- try {
35
- const data = await fetcherRef.current!();
36
- cache.set(key, data, configRef.current?.ttl);
37
- setValue(data);
38
- } catch (err) {
39
- setError(err as Error);
40
- } finally {
41
- setIsLoading(false);
42
- }
43
- }, [cacheName, key]);
44
-
45
- useEffect(() => {
46
- let isMounted = true;
47
-
48
- const doLoad = async () => {
19
+ const { data: value, isLoading, error, execute, setData } = useAsyncOperation<T | undefined, Error>(
20
+ async () => {
49
21
  const cache = cacheManager.getCache<T>(cacheName, configRef.current);
50
22
  const cached = cache.get(key);
51
23
 
52
24
  if (cached !== undefined) {
53
- if (isMounted) setValue(cached);
54
- return;
55
- }
56
-
57
- if (isMounted) {
58
- setIsLoading(true);
59
- setError(null);
60
- }
61
-
62
- try {
63
- const data = await fetcherRef.current!();
64
- cache.set(key, data, configRef.current?.ttl);
65
- if (isMounted) setValue(data);
66
- } catch (err) {
67
- if (isMounted) setError(err as Error);
68
- } finally {
69
- if (isMounted) setIsLoading(false);
25
+ return cached;
70
26
  }
71
- };
72
27
 
73
- doLoad();
74
-
75
- return () => {
76
- isMounted = false;
77
- };
78
- }, [cacheName, key]);
28
+ const data = await fetcherRef.current!();
29
+ cache.set(key, data, configRef.current?.ttl);
30
+ return data;
31
+ },
32
+ {
33
+ immediate: true,
34
+ initialData: undefined,
35
+ errorHandler: (err) => err as Error,
36
+ }
37
+ );
79
38
 
80
39
  const invalidate = useCallback(() => {
81
40
  const cache = cacheManager.getCache<T>(cacheName);
82
41
  cache.delete(key);
83
- setValue(undefined);
84
- }, [cacheName, key]);
42
+ setData(undefined);
43
+ }, [cacheName, key, setData]);
85
44
 
86
45
  const invalidatePattern = useCallback((pattern: string): number => {
87
46
  const cache = cacheManager.getCache<T>(cacheName);
88
47
  const count = cache.invalidatePattern(pattern);
89
- setValue(undefined);
48
+ setData(undefined);
90
49
  return count;
91
- }, [cacheName]);
50
+ }, [cacheName, setData]);
92
51
 
93
52
  const refetch = useCallback(() => {
94
- setValue(undefined);
95
- loadValue();
96
- }, [loadValue]);
53
+ setData(undefined);
54
+ execute();
55
+ }, [execute, setData]);
97
56
 
98
- return {
57
+ return useMemo(() => ({
99
58
  value,
100
59
  isLoading,
101
60
  error,
102
61
  invalidate,
103
62
  invalidatePattern,
104
63
  refetch,
105
- };
64
+ }), [value, isLoading, error, invalidate, invalidatePattern, refetch]);
106
65
  }
@@ -5,10 +5,11 @@
5
5
  * Combines React state with automatic storage persistence
6
6
  */
7
7
 
8
- import { useState, useEffect, useCallback } from 'react';
8
+ import { useState, useCallback, useEffect } from 'react';
9
9
  import { storageRepository } from '../../infrastructure/repositories/AsyncStorageRepository';
10
10
  import { unwrap } from '../../domain/entities/StorageResult';
11
11
  import type { StorageKey } from '../../domain/value-objects/StorageKey';
12
+ import { useAsyncOperation } from '../../../utils/hooks';
12
13
 
13
14
  /**
14
15
  * Storage State Hook
@@ -26,37 +27,27 @@ export const useStorageState = <T>(
26
27
  ): [T, (value: T) => Promise<void>, boolean] => {
27
28
  const keyString = typeof key === 'string' ? key : String(key);
28
29
  const [state, setState] = useState<T>(defaultValue);
29
- const [isLoading, setIsLoading] = useState(true);
30
30
 
31
31
  // Load initial value from storage
32
- useEffect(() => {
33
- let isMounted = true;
34
-
35
- const loadFromStorage = async () => {
36
- try {
37
- const result = await storageRepository.getItem(keyString, defaultValue);
38
- const value = unwrap(result, defaultValue);
39
-
40
- // Memory leak önlemek için component mount kontrolü
41
- if (isMounted) {
42
- setState(value);
43
- setIsLoading(false);
44
- }
45
- } catch {
46
- // Hata durumunda bile cleanup yap
47
- if (isMounted) {
48
- setIsLoading(false);
49
- }
50
- }
51
- };
52
-
53
- loadFromStorage();
32
+ const { data, isLoading } = useAsyncOperation<T, Error>(
33
+ async () => {
34
+ const result = await storageRepository.getItem(keyString, defaultValue);
35
+ return unwrap(result, defaultValue);
36
+ },
37
+ {
38
+ immediate: true,
39
+ initialData: defaultValue,
40
+ errorHandler: (err) => err as Error,
41
+ onSuccess: (value) => setState(value),
42
+ }
43
+ );
54
44
 
55
- // Cleanup function
56
- return () => {
57
- isMounted = false;
58
- };
59
- }, [keyString]); // defaultValue'ı dependency array'den çıkar
45
+ // Sync state with loaded data
46
+ useEffect(() => {
47
+ if (data !== undefined && data !== null) {
48
+ setState(data);
49
+ }
50
+ }, [data]);
60
51
 
61
52
  // Update state and persist to storage
62
53
  const updateState = useCallback(
@@ -8,174 +8,109 @@
8
8
  * @layer presentation/hooks
9
9
  */
10
10
 
11
- import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
11
+ import { useCallback, useMemo } from 'react';
12
12
  import { SharingService } from '../../infrastructure/services/SharingService';
13
13
  import type { ShareOptions } from '../../domain/entities/Share';
14
+ import { useAsyncOperation } from '../../../../utils/hooks';
14
15
 
15
16
  /**
16
17
  * useSharing hook for sharing files via system share sheet
17
- *
18
- * USAGE:
19
- * ```typescript
20
- * const { share, shareWithAutoType, isAvailable, isSharing } = useSharing();
21
- *
22
- * // Check availability
23
- * if (!isAvailable) {
24
- * return <Text>Sharing not available</Text>;
25
- * }
26
- *
27
- * // Basic share
28
- * const handleShare = async () => {
29
- * await share('file:///path/to/file.jpg', {
30
- * dialogTitle: 'Share Photo',
31
- * mimeType: 'image/jpeg',
32
- * });
33
- * };
34
- *
35
- * // Auto-detect MIME type
36
- * const handleShareAuto = async () => {
37
- * await shareWithAutoType(
38
- * 'file:///path/to/document.pdf',
39
- * 'document.pdf',
40
- * 'Share Document'
41
- * );
42
- * };
43
- * ```
44
18
  */
45
19
  export const useSharing = () => {
46
- const [isAvailable, setIsAvailable] = useState(false);
47
- const [isSharing, setIsSharing] = useState(false);
48
- const [error, setError] = useState<string | null>(null);
49
-
50
- // Track mounted state to prevent setState on unmounted component
51
- const isMountedRef = useRef(true);
52
-
53
- /**
54
- * Check sharing availability on mount
55
- */
56
- useEffect(() => {
57
- const checkAvailability = async () => {
58
- const available = await SharingService.isAvailable();
59
- if (isMountedRef.current) {
60
- setIsAvailable(available);
61
- }
62
- };
63
-
64
- checkAvailability();
65
-
66
- return () => {
67
- isMountedRef.current = false;
68
- };
69
- }, []);
70
-
71
- /**
72
- * Share a file via system share sheet
73
- */
74
- const share = useCallback(async (uri: string, options?: ShareOptions): Promise<boolean> => {
75
- if (isMountedRef.current) {
76
- setIsSharing(true);
77
- setError(null);
20
+ // Check availability on mount
21
+ const availabilityOp = useAsyncOperation<boolean, string>(
22
+ () => SharingService.isAvailable(),
23
+ {
24
+ immediate: true,
25
+ initialData: false,
26
+ errorHandler: () => 'Failed to check sharing availability',
78
27
  }
28
+ );
79
29
 
80
- try {
30
+ // Share operations
31
+ const shareOp = useAsyncOperation<boolean, string>(
32
+ async (uri: string, options?: ShareOptions) => {
81
33
  const result = await SharingService.shareFile(uri, options);
82
-
83
- if (!result.success && isMountedRef.current) {
84
- setError(result.error || 'Failed to share file');
85
- return false;
34
+ if (!result.success) {
35
+ throw new Error(result.error || 'Failed to share file');
86
36
  }
87
-
88
37
  return true;
89
- } catch (err) {
90
- const errorMessage = err instanceof Error ? err.message : 'Failed to share file';
91
- if (isMountedRef.current) {
92
- setError(errorMessage);
93
- }
94
- return false;
95
- } finally {
96
- if (isMountedRef.current) {
97
- setIsSharing(false);
98
- }
38
+ },
39
+ {
40
+ immediate: false,
41
+ errorHandler: (err) => err instanceof Error ? err.message : 'Failed to share file',
99
42
  }
100
- }, []);
43
+ );
101
44
 
102
- /**
103
- * Share file with automatic MIME type detection
104
- */
105
- const shareWithAutoType = useCallback(
106
- async (uri: string, filename: string, dialogTitle?: string): Promise<boolean> => {
107
- if (isMountedRef.current) {
108
- setIsSharing(true);
109
- setError(null);
45
+ const shareWithAutoTypeOp = useAsyncOperation<boolean, string>(
46
+ async (uri: string, filename: string, dialogTitle?: string) => {
47
+ const result = await SharingService.shareWithAutoType(uri, filename, dialogTitle);
48
+ if (!result.success) {
49
+ throw new Error(result.error || 'Failed to share file');
110
50
  }
51
+ return true;
52
+ },
53
+ {
54
+ immediate: false,
55
+ errorHandler: (err) => err instanceof Error ? err.message : 'Failed to share file',
56
+ }
57
+ );
111
58
 
112
- try {
113
- const result = await SharingService.shareWithAutoType(uri, filename, dialogTitle);
59
+ const shareMultipleOp = useAsyncOperation<boolean, string>(
60
+ async (uris: string[], options?: ShareOptions) => {
61
+ const result = await SharingService.shareMultipleFiles(uris, options);
62
+ if (!result.success) {
63
+ throw new Error(result.error || 'Failed to share files');
64
+ }
65
+ return true;
66
+ },
67
+ {
68
+ immediate: false,
69
+ errorHandler: (err) => err instanceof Error ? err.message : 'Failed to share files',
70
+ }
71
+ );
114
72
 
115
- if (!result.success && isMountedRef.current) {
116
- setError(result.error || 'Failed to share file');
117
- return false;
118
- }
73
+ const share = useCallback(
74
+ async (uri: string, options?: ShareOptions): Promise<boolean> => {
75
+ const result = await shareOp.execute(uri, options);
76
+ return result ?? false;
77
+ },
78
+ [shareOp]
79
+ );
119
80
 
120
- return true;
121
- } catch (err) {
122
- const errorMessage = err instanceof Error ? err.message : 'Failed to share file';
123
- if (isMountedRef.current) {
124
- setError(errorMessage);
125
- }
126
- return false;
127
- } finally {
128
- if (isMountedRef.current) {
129
- setIsSharing(false);
130
- }
131
- }
81
+ const shareWithAutoType = useCallback(
82
+ async (uri: string, filename: string, dialogTitle?: string): Promise<boolean> => {
83
+ const result = await shareWithAutoTypeOp.execute(uri, filename, dialogTitle);
84
+ return result ?? false;
132
85
  },
133
- []
86
+ [shareWithAutoTypeOp]
134
87
  );
135
88
 
136
- /**
137
- * Share multiple files
138
- */
139
89
  const shareMultiple = useCallback(
140
90
  async (uris: string[], options?: ShareOptions): Promise<boolean> => {
141
- if (isMountedRef.current) {
142
- setIsSharing(true);
143
- setError(null);
144
- }
145
-
146
- try {
147
- const result = await SharingService.shareMultipleFiles(uris, options);
148
-
149
- if (!result.success && isMountedRef.current) {
150
- setError(result.error || 'Failed to share files');
151
- return false;
152
- }
153
-
154
- return true;
155
- } catch (err) {
156
- const errorMessage = err instanceof Error ? err.message : 'Failed to share files';
157
- if (isMountedRef.current) {
158
- setError(errorMessage);
159
- }
160
- return false;
161
- } finally {
162
- if (isMountedRef.current) {
163
- setIsSharing(false);
164
- }
165
- }
91
+ const result = await shareMultipleOp.execute(uris, options);
92
+ return result ?? false;
166
93
  },
167
- []
94
+ [shareMultipleOp]
168
95
  );
169
96
 
170
97
  return useMemo(() => ({
171
- // Functions
172
98
  share,
173
99
  shareWithAutoType,
174
100
  shareMultiple,
175
-
176
- // State
177
- isAvailable,
178
- isSharing,
179
- error,
180
- }), [share, shareWithAutoType, shareMultiple, isAvailable, isSharing, error]);
101
+ isAvailable: availabilityOp.data ?? false,
102
+ isSharing: shareOp.isLoading || shareWithAutoTypeOp.isLoading || shareMultipleOp.isLoading,
103
+ error: shareOp.error || shareWithAutoTypeOp.error || shareMultipleOp.error,
104
+ }), [
105
+ share,
106
+ shareWithAutoType,
107
+ shareMultiple,
108
+ availabilityOp.data,
109
+ shareOp.isLoading,
110
+ shareWithAutoTypeOp.isLoading,
111
+ shareMultipleOp.isLoading,
112
+ shareOp.error,
113
+ shareWithAutoTypeOp.error,
114
+ shareMultipleOp.error,
115
+ ]);
181
116
  };
@@ -21,10 +21,31 @@ export class DesignSystemError extends Error {
21
21
  */
22
22
  public readonly timestamp: Date;
23
23
 
24
+ /**
25
+ * Error category for grouping
26
+ */
27
+ public readonly category: ErrorCategory;
28
+
29
+ /**
30
+ * Operation that failed
31
+ */
32
+ public readonly operation?: string;
33
+
34
+ /**
35
+ * Original error cause
36
+ */
37
+ public readonly cause?: unknown;
38
+
39
+ /**
40
+ * Whether operation can be retried
41
+ */
42
+ public readonly retryable: boolean;
43
+
24
44
  constructor(
25
45
  message: string,
26
46
  code: string,
27
- context?: Record<string, any>
47
+ context?: Record<string, any>,
48
+ metadata?: ErrorMetadata
28
49
  ) {
29
50
  super(message);
30
51
  this.name = 'DesignSystemError';
@@ -32,6 +53,12 @@ export class DesignSystemError extends Error {
32
53
  this.context = context;
33
54
  this.timestamp = new Date();
34
55
 
56
+ // Extract metadata
57
+ this.category = metadata?.category ?? ErrorCategory.UNKNOWN;
58
+ this.operation = metadata?.operation;
59
+ this.cause = metadata?.cause;
60
+ this.retryable = metadata?.retryable ?? false;
61
+
35
62
  // Maintains proper stack trace for where our error was thrown (only available on V8)
36
63
  if (Error.captureStackTrace) {
37
64
  Error.captureStackTrace(this, DesignSystemError);
@@ -115,3 +142,32 @@ export const ErrorCodes = {
115
142
  } as const;
116
143
 
117
144
  export type ErrorCode = typeof ErrorCodes[keyof typeof ErrorCodes];
145
+
146
+ /**
147
+ * Error Categories for grouping related errors
148
+ */
149
+ export enum ErrorCategory {
150
+ FILESYSTEM = 'FILESYSTEM',
151
+ NETWORK = 'NETWORK',
152
+ MEDIA = 'MEDIA',
153
+ STORAGE = 'STORAGE',
154
+ CACHE = 'CACHE',
155
+ IMAGE = 'IMAGE',
156
+ VALIDATION = 'VALIDATION',
157
+ THEME = 'THEME',
158
+ PERMISSION = 'PERMISSION',
159
+ INITIALIZATION = 'INITIALIZATION',
160
+ UNKNOWN = 'UNKNOWN',
161
+ }
162
+
163
+ /**
164
+ * Error metadata for rich context
165
+ */
166
+ export interface ErrorMetadata {
167
+ category?: ErrorCategory;
168
+ operation?: string;
169
+ key?: string;
170
+ cause?: unknown;
171
+ retryable?: boolean;
172
+ userMessage?: string;
173
+ }
@@ -5,7 +5,9 @@
5
5
  * Provides consistent error handling, logging, and reporting.
6
6
  */
7
7
 
8
- import { DesignSystemError, ErrorCodes } from './DesignSystemError';
8
+ import { DesignSystemError, ErrorCodes, ErrorCategory, type ErrorMetadata } from './DesignSystemError';
9
+ import type { Result } from './types/Result';
10
+ import { ok, err } from './types/Result';
9
11
 
10
12
  export class ErrorHandler {
11
13
  /**
@@ -134,4 +136,106 @@ export class ErrorHandler {
134
136
  return [handled, null];
135
137
  }
136
138
  }
139
+
140
+ /**
141
+ * Normalize any error to DesignSystemError with metadata
142
+ */
143
+ static normalize(
144
+ error: unknown,
145
+ code: string,
146
+ metadata?: ErrorMetadata
147
+ ): DesignSystemError {
148
+ if (error instanceof DesignSystemError) {
149
+ return error;
150
+ }
151
+
152
+ if (error instanceof Error) {
153
+ return new DesignSystemError(
154
+ error.message,
155
+ code,
156
+ {
157
+ originalError: error.name,
158
+ stack: error.stack,
159
+ },
160
+ {
161
+ ...metadata,
162
+ cause: error,
163
+ }
164
+ );
165
+ }
166
+
167
+ if (typeof error === 'string') {
168
+ return new DesignSystemError(error, code, undefined, metadata);
169
+ }
170
+
171
+ return new DesignSystemError(
172
+ 'An unknown error occurred',
173
+ code,
174
+ { originalError: String(error) },
175
+ metadata
176
+ );
177
+ }
178
+
179
+ /**
180
+ * Wrap async function with timeout
181
+ */
182
+ static async withTimeout<T>(
183
+ promise: Promise<T>,
184
+ timeoutMs: number,
185
+ context?: string
186
+ ): Promise<T> {
187
+ const timeoutPromise = new Promise<never>((_, reject) => {
188
+ setTimeout(() => {
189
+ reject(
190
+ new DesignSystemError(
191
+ `${context || 'Operation'} timed out after ${timeoutMs}ms`,
192
+ ErrorCodes.TIMEOUT_ERROR,
193
+ { timeoutMs, context },
194
+ {
195
+ category: ErrorCategory.NETWORK,
196
+ retryable: true,
197
+ }
198
+ )
199
+ );
200
+ }, timeoutMs);
201
+ });
202
+
203
+ return Promise.race([promise, timeoutPromise]);
204
+ }
205
+
206
+ /**
207
+ * Wrap async function returning Result type
208
+ */
209
+ static async tryAsyncResult<T>(
210
+ fn: () => Promise<T>,
211
+ code: string,
212
+ metadata?: ErrorMetadata
213
+ ): Promise<Result<T, DesignSystemError>> {
214
+ try {
215
+ const result = await fn();
216
+ return ok(result);
217
+ } catch (error) {
218
+ const normalized = this.normalize(error, code, metadata);
219
+ this.log(normalized);
220
+ return err(normalized);
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Wrap sync function returning Result type
226
+ */
227
+ static tryResult<T>(
228
+ fn: () => T,
229
+ code: string,
230
+ metadata?: ErrorMetadata
231
+ ): Result<T, DesignSystemError> {
232
+ try {
233
+ const result = fn();
234
+ return ok(result);
235
+ } catch (error) {
236
+ const normalized = this.normalize(error, code, metadata);
237
+ this.log(normalized);
238
+ return err(normalized);
239
+ }
240
+ }
137
241
  }