@umituz/react-native-design-system 4.23.79 → 4.23.81
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 +10 -8
- package/src/atoms/AtomicInput.tsx +11 -48
- package/src/atoms/AtomicPicker.tsx +19 -94
- package/src/atoms/EmptyState.tsx +1 -1
- package/src/atoms/icon/AtomicIcon.tsx +0 -1
- package/src/atoms/icon/iconStore.ts +0 -2
- package/src/atoms/picker/components/PickerModal.tsx +40 -121
- package/src/device/infrastructure/services/PersistentDeviceIdService.ts +0 -4
- package/src/device/presentation/hooks/useDeviceInfo.ts +55 -149
- package/src/haptics/infrastructure/services/HapticService.ts +0 -1
- package/src/image/index.ts +2 -1
- package/src/image/presentation/hooks/useImageBatch.ts +2 -1
- package/src/index.ts +2 -2
- package/src/infinite-scroll/presentation/components/infinite-scroll-list.tsx +1 -1
- package/src/infinite-scroll/presentation/hooks/pagination.helper.ts +0 -5
- package/src/infinite-scroll/presentation/hooks/useInfiniteScroll.ts +4 -54
- package/src/init/createAppInitializer.ts +0 -1
- package/src/init/env/createEnvConfig.ts +0 -1
- package/src/init/useAppInitialization.ts +0 -1
- package/src/layouts/ScreenHeader/ScreenHeader.tsx +1 -1
- package/src/media/infrastructure/services/CardMediaOptimizerService.ts +0 -1
- package/src/media/infrastructure/services/CardMediaUploadService.ts +0 -1
- package/src/media/presentation/hooks/useCardMediaGeneration.ts +1 -1
- package/src/media/presentation/hooks/useCardMediaUpload.ts +1 -1
- package/src/media/presentation/hooks/useCardMediaValidation.ts +1 -1
- package/src/media/presentation/hooks/useCardMultimediaFlashcard.ts +1 -1
- package/src/media/presentation/hooks/useMedia.ts +0 -1
- package/src/media/presentation/hooks/useMediaGeneration.ts +1 -1
- package/src/media/presentation/hooks/useMediaUpload.ts +1 -1
- package/src/media/presentation/hooks/useMediaValidation.ts +1 -1
- package/src/media/presentation/hooks/useMultimediaFlashcard.ts +1 -1
- package/src/molecules/BaseModal.tsx +1 -3
- package/src/molecules/ConfirmationModalContent.tsx +1 -1
- package/src/molecules/ConfirmationModalMain.tsx +1 -1
- package/src/molecules/bottom-sheet/components/BottomSheetModal.tsx +0 -3
- package/src/molecules/bottom-sheet/components/filter/FilterBottomSheet.tsx +100 -179
- package/src/molecules/calendar/infrastructure/stores/useCalendarEvents.ts +0 -1
- package/src/molecules/confirmation-modal/useConfirmationModal.ts +1 -1
- package/src/molecules/countdown/components/Countdown.tsx +1 -1
- package/src/molecules/navigation/StackNavigator.tsx +0 -1
- package/src/molecules/navigation/TabsNavigator.tsx +0 -1
- package/src/molecules/navigation/utils/AppNavigation.ts +0 -8
- package/src/molecules/splash/components/SplashScreen.tsx +0 -4
- package/src/offline/infrastructure/events/NetworkEvents.ts +0 -1
- package/src/offline/infrastructure/utils/healthCheck.ts +0 -5
- package/src/offline/presentation/hooks/useOffline.ts +0 -1
- package/src/offline/presentation/hooks/useOfflineWithMutations.ts +0 -2
- package/src/onboarding/index.ts +0 -1
- package/src/onboarding/infrastructure/hooks/useOnboardingNavigation.ts +0 -1
- package/src/onboarding/infrastructure/storage/actions/storageHelpers.ts +0 -2
- package/src/onboarding/presentation/hooks/useOnboardingScreenHandlers.ts +0 -2
- package/src/onboarding/presentation/hooks/useOnboardingScreenState.ts +0 -1
- package/src/onboarding/presentation/screens/OnboardingScreen.tsx +0 -3
- package/src/organisms/FormContainer.tsx +1 -1
- package/src/services/api/ApiClient.ts +42 -135
- package/src/storage/cache/domain/Cache.ts +0 -2
- package/src/storage/cache/infrastructure/TTLCache.ts +0 -3
- package/src/storage/domain/utils/devUtils.ts +0 -3
- package/src/storage/infrastructure/adapters/StorageService.ts +0 -3
- package/src/storage/infrastructure/repositories/BaseStorageOperations.ts +0 -1
- package/src/tanstack/domain/config/QueryClientAccessor.ts +0 -1
- package/src/tanstack/domain/repositories/BaseRepository.ts +4 -4
- package/src/tanstack/domain/repositories/IBaseRepository.ts +3 -5
- package/src/tanstack/domain/repositories/RepositoryFactory.ts +0 -2
- package/src/tanstack/domain/repositories/mixins/repositoryInvalidationMethods.ts +10 -11
- package/src/tanstack/domain/repositories/mixins/repositoryQueryMethods.ts +11 -11
- package/src/tanstack/domain/utils/ErrorHelpers.ts +0 -1
- package/src/tanstack/infrastructure/config/PersisterConfig.ts +0 -7
- package/src/tanstack/infrastructure/config/QueryClientConfig.ts +0 -1
- package/src/tanstack/infrastructure/monitoring/DevMonitorLogger.ts +0 -6
- package/src/tanstack/presentation/hooks/useInvalidateQueries.ts +0 -4
- package/src/tanstack/presentation/hooks/useOptimisticUpdate.ts +0 -2
- package/src/tanstack/presentation/hooks/usePrefetch.ts +18 -119
- package/src/theme/core/CustomColors.ts +4 -122
- package/src/theme/infrastructure/storage/ThemeStorage.ts +0 -1
- package/src/typography/presentation/utils/textColorUtils.ts +36 -163
- package/src/exception/domain/entities/ExceptionEntity.ts +0 -115
- package/src/exception/domain/repositories/IExceptionRepository.ts +0 -37
- package/src/exception/index.ts +0 -66
- package/src/exception/infrastructure/services/ExceptionHandler.ts +0 -93
- package/src/exception/infrastructure/services/ExceptionLogger.ts +0 -142
- package/src/exception/infrastructure/services/ExceptionReporter.ts +0 -134
- package/src/exception/infrastructure/services/ExceptionService.ts +0 -166
- package/src/exception/infrastructure/storage/ExceptionStore.ts +0 -44
- package/src/exception/presentation/components/ErrorBoundary.tsx +0 -129
- package/src/exception/presentation/components/ExceptionEmptyState.tsx +0 -123
- package/src/exception/presentation/components/ExceptionErrorState.tsx +0 -122
- package/src/tanstack/presentation/hooks/utils/prefetchLogger.ts +0 -27
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Exception Reporter Service
|
|
3
|
-
*
|
|
4
|
-
* Handles reporting exceptions to external services.
|
|
5
|
-
*
|
|
6
|
-
* SOLID: Single Responsibility - Only exception reporting
|
|
7
|
-
* DRY: Centralized reporting logic
|
|
8
|
-
* KISS: Simple reporting interface
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { ExceptionEntity } from '../../domain/entities/ExceptionEntity';
|
|
12
|
-
|
|
13
|
-
export interface ExceptionReporterConfig {
|
|
14
|
-
enabled: boolean;
|
|
15
|
-
endpoint?: string;
|
|
16
|
-
apiKey?: string;
|
|
17
|
-
environment: 'development' | 'staging' | 'production';
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export class ExceptionReporter {
|
|
21
|
-
private config: ExceptionReporterConfig;
|
|
22
|
-
|
|
23
|
-
constructor(config: ExceptionReporterConfig) {
|
|
24
|
-
this.config = config;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Report exception to external service
|
|
29
|
-
*/
|
|
30
|
-
async reportException(exception: ExceptionEntity): Promise<boolean> {
|
|
31
|
-
if (!this.config.enabled) {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
// Default console reporting for development
|
|
37
|
-
if (this.config.environment === 'development') {
|
|
38
|
-
return this.reportToConsole(exception);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// External service reporting
|
|
42
|
-
if (this.config.endpoint) {
|
|
43
|
-
return await this.reportToExternalService(exception);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return false;
|
|
47
|
-
} catch (error) {
|
|
48
|
-
// Don't throw in reporter to avoid infinite loops
|
|
49
|
-
console.warn('Exception reporting failed:', error);
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Report to console (development)
|
|
56
|
-
*/
|
|
57
|
-
private reportToConsole(exception: ExceptionEntity): boolean {
|
|
58
|
-
const level = this.getConsoleLevel(exception.severity);
|
|
59
|
-
|
|
60
|
-
console[level](
|
|
61
|
-
`[${exception.severity.toUpperCase()}] ${exception.category}:`,
|
|
62
|
-
exception.message,
|
|
63
|
-
{
|
|
64
|
-
id: exception.id,
|
|
65
|
-
timestamp: exception.timestamp,
|
|
66
|
-
context: exception.context,
|
|
67
|
-
stack: exception.stackTrace,
|
|
68
|
-
}
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Report to external service
|
|
76
|
-
*/
|
|
77
|
-
private async reportToExternalService(exception: ExceptionEntity): Promise<boolean> {
|
|
78
|
-
if (!this.config.endpoint) {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const response = await fetch(this.config.endpoint, {
|
|
83
|
-
method: 'POST',
|
|
84
|
-
headers: {
|
|
85
|
-
'Content-Type': 'application/json',
|
|
86
|
-
...(this.config.apiKey && { 'Authorization': `Bearer ${this.config.apiKey}` }),
|
|
87
|
-
},
|
|
88
|
-
body: JSON.stringify({
|
|
89
|
-
id: exception.id,
|
|
90
|
-
message: exception.message,
|
|
91
|
-
severity: exception.severity,
|
|
92
|
-
category: exception.category,
|
|
93
|
-
timestamp: exception.timestamp,
|
|
94
|
-
context: exception.context,
|
|
95
|
-
stackTrace: exception.stackTrace,
|
|
96
|
-
environment: this.config.environment,
|
|
97
|
-
}),
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
return response.ok;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Get console level for severity
|
|
105
|
-
*/
|
|
106
|
-
private getConsoleLevel(severity: ExceptionEntity['severity']): 'log' | 'warn' | 'error' {
|
|
107
|
-
switch (severity) {
|
|
108
|
-
case 'fatal':
|
|
109
|
-
case 'error':
|
|
110
|
-
return 'error';
|
|
111
|
-
case 'warning':
|
|
112
|
-
return 'warn';
|
|
113
|
-
default:
|
|
114
|
-
return 'log';
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Update reporter configuration
|
|
120
|
-
*/
|
|
121
|
-
updateConfig(config: Partial<ExceptionReporterConfig>): void {
|
|
122
|
-
this.config = { ...this.config, ...config };
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Exception Service - Infrastructure Layer
|
|
3
|
-
*
|
|
4
|
-
* Facade for exception handling using composition.
|
|
5
|
-
* Delegates to specialized services for specific operations.
|
|
6
|
-
*
|
|
7
|
-
* SOLID: Facade pattern - Single entry point, delegates to specialists
|
|
8
|
-
* DRY: Avoids code duplication by composing smaller services
|
|
9
|
-
* KISS: Simple interface, complex operations delegated
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type {
|
|
13
|
-
ExceptionEntity,
|
|
14
|
-
ExceptionContext,
|
|
15
|
-
ExceptionSeverity,
|
|
16
|
-
ExceptionCategory,
|
|
17
|
-
} from '../../domain/entities/ExceptionEntity';
|
|
18
|
-
import { ExceptionHandler } from './ExceptionHandler';
|
|
19
|
-
import { ExceptionReporter } from './ExceptionReporter';
|
|
20
|
-
import { ExceptionLogger } from './ExceptionLogger';
|
|
21
|
-
import { useExceptionStore } from '../storage/ExceptionStore';
|
|
22
|
-
|
|
23
|
-
export class ExceptionService {
|
|
24
|
-
private logger: ExceptionLogger | null = null;
|
|
25
|
-
private reporter: ExceptionReporter | null = null;
|
|
26
|
-
private reporterConfig: ExceptionReporter['config'];
|
|
27
|
-
|
|
28
|
-
constructor(reporterConfig?: ExceptionReporter['config']) {
|
|
29
|
-
this.reporterConfig = reporterConfig || {
|
|
30
|
-
enabled: false,
|
|
31
|
-
environment: 'development'
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
private ensureInitialized() {
|
|
36
|
-
if (!this.logger) {
|
|
37
|
-
this.logger = new ExceptionLogger();
|
|
38
|
-
this.reporter = new ExceptionReporter(this.reporterConfig);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Handle an exception
|
|
44
|
-
*/
|
|
45
|
-
async handleException(
|
|
46
|
-
error: Error,
|
|
47
|
-
severity: ExceptionSeverity = 'error',
|
|
48
|
-
category: ExceptionCategory = 'unknown',
|
|
49
|
-
context: ExceptionContext = {},
|
|
50
|
-
): Promise<void> {
|
|
51
|
-
this.ensureInitialized();
|
|
52
|
-
|
|
53
|
-
const exception = ExceptionHandler.createException(error, severity, category, context);
|
|
54
|
-
|
|
55
|
-
// Add to store
|
|
56
|
-
useExceptionStore.getState().addException(exception);
|
|
57
|
-
|
|
58
|
-
// Log locally
|
|
59
|
-
await this.logger!.logException(exception);
|
|
60
|
-
|
|
61
|
-
// Report to external service if needed
|
|
62
|
-
if (ExceptionHandler.shouldReportException(exception)) {
|
|
63
|
-
await this.reporter!.reportException(exception);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Mark as handled
|
|
67
|
-
useExceptionStore.getState().markAsHandled(exception.id);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Handle network errors
|
|
72
|
-
*/
|
|
73
|
-
async handleNetworkError(error: Error, context: ExceptionContext = {}): Promise<void> {
|
|
74
|
-
await this.handleException(error, 'error', 'network', context);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Handle validation errors
|
|
79
|
-
*/
|
|
80
|
-
async handleValidationError(error: Error, context: ExceptionContext = {}): Promise<void> {
|
|
81
|
-
await this.handleException(error, 'warning', 'validation', context);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Handle authentication errors
|
|
86
|
-
*/
|
|
87
|
-
async handleAuthError(error: Error, context: ExceptionContext = {}): Promise<void> {
|
|
88
|
-
await this.handleException(error, 'error', 'authentication', context);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Handle system errors
|
|
93
|
-
*/
|
|
94
|
-
async handleSystemError(error: Error, context: ExceptionContext = {}): Promise<void> {
|
|
95
|
-
await this.handleException(error, 'fatal', 'system', context);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Get stored exceptions
|
|
100
|
-
*/
|
|
101
|
-
async getStoredExceptions(): Promise<ExceptionEntity[]> {
|
|
102
|
-
this.ensureInitialized();
|
|
103
|
-
return this.logger!.getStoredExceptions();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Get exception statistics
|
|
108
|
-
*/
|
|
109
|
-
async getExceptionStats() {
|
|
110
|
-
this.ensureInitialized();
|
|
111
|
-
return this.logger!.getExceptionStats();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Clear stored exceptions
|
|
116
|
-
*/
|
|
117
|
-
async clearStoredExceptions(): Promise<void> {
|
|
118
|
-
this.ensureInitialized();
|
|
119
|
-
await this.logger!.clearStoredExceptions();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Update reporter configuration
|
|
124
|
-
*/
|
|
125
|
-
updateReporterConfig(config: Partial<ExceptionReporter['config']>): void {
|
|
126
|
-
this.ensureInitialized();
|
|
127
|
-
this.reporter!.updateConfig(config);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Set max stored exceptions
|
|
132
|
-
*/
|
|
133
|
-
setMaxStoredExceptions(limit: number): void {
|
|
134
|
-
this.ensureInitialized();
|
|
135
|
-
this.logger!.setMaxStoredExceptions(limit);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Handle storage/permission errors
|
|
140
|
-
*/
|
|
141
|
-
async handleStorageError(error: Error, context: ExceptionContext = {}): Promise<void> {
|
|
142
|
-
await this.handleException(error, 'error', 'storage', context);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Handle fatal errors
|
|
147
|
-
*/
|
|
148
|
-
async handleFatalError(error: Error, context: ExceptionContext = {}): Promise<void> {
|
|
149
|
-
await this.handleException(error, 'fatal', 'system', context);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Clear all exceptions
|
|
154
|
-
*/
|
|
155
|
-
clearExceptions(): void {
|
|
156
|
-
useExceptionStore.getState().clearExceptions();
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Export default instance - lazy initialization
|
|
161
|
-
export const exceptionService = new ExceptionService();
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Exception Store
|
|
3
|
-
* Zustand store for exception state management
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { createStore } from '../../../storage';
|
|
7
|
-
import type { ExceptionEntity } from '../../domain/entities/ExceptionEntity';
|
|
8
|
-
|
|
9
|
-
interface ExceptionState {
|
|
10
|
-
exceptions: ExceptionEntity[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface ExceptionActions {
|
|
14
|
-
addException: (exception: ExceptionEntity) => void;
|
|
15
|
-
clearExceptions: () => void;
|
|
16
|
-
markAsHandled: (id: string) => void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const useExceptionStore = createStore<ExceptionState, ExceptionActions>({
|
|
20
|
-
name: 'exception-store',
|
|
21
|
-
initialState: {
|
|
22
|
-
exceptions: [],
|
|
23
|
-
},
|
|
24
|
-
persist: false,
|
|
25
|
-
actions: (set: (state: Partial<ExceptionState>) => void, get: () => ExceptionState) => ({
|
|
26
|
-
addException: (exception: ExceptionEntity) =>
|
|
27
|
-
set({ exceptions: [...get().exceptions, exception] }),
|
|
28
|
-
clearExceptions: () => set({ exceptions: [] }),
|
|
29
|
-
markAsHandled: (id: string) =>
|
|
30
|
-
set({
|
|
31
|
-
exceptions: get().exceptions.map((e: ExceptionEntity) =>
|
|
32
|
-
e.id === id ? { ...e, handled: true } : e
|
|
33
|
-
),
|
|
34
|
-
}),
|
|
35
|
-
}),
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Hook to get exceptions from store
|
|
40
|
-
*/
|
|
41
|
-
export const useExceptions = () => {
|
|
42
|
-
const exceptions = useExceptionStore((state: ExceptionState) => state.exceptions);
|
|
43
|
-
return exceptions;
|
|
44
|
-
};
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Error Boundary Component
|
|
3
|
-
* React 19 compatible - Functional component with hooks
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { ReactNode, useState, useCallback } from 'react';
|
|
7
|
-
import { View, StyleSheet, TouchableOpacity } from 'react-native';
|
|
8
|
-
import { AtomicText } from '../../../atoms';
|
|
9
|
-
import { exceptionService } from '../../infrastructure/services/ExceptionService';
|
|
10
|
-
import { useAppDesignTokens } from '../../../theme';
|
|
11
|
-
|
|
12
|
-
interface Props {
|
|
13
|
-
children: ReactNode;
|
|
14
|
-
fallback?: ReactNode;
|
|
15
|
-
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface ErrorInfo {
|
|
19
|
-
error: Error;
|
|
20
|
-
componentStack: string | null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const ErrorBoundary: React.FC<Props> = ({
|
|
24
|
-
children,
|
|
25
|
-
fallback,
|
|
26
|
-
onError
|
|
27
|
-
}) => {
|
|
28
|
-
const [errorState, setErrorState] = useState<{
|
|
29
|
-
hasError: boolean;
|
|
30
|
-
error: Error | null;
|
|
31
|
-
}>({
|
|
32
|
-
hasError: false,
|
|
33
|
-
error: null,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const tokens = useAppDesignTokens();
|
|
37
|
-
|
|
38
|
-
// Global error handler for React 19
|
|
39
|
-
useCallback((error: Error, errorInfo: React.ErrorInfo) => {
|
|
40
|
-
// Log error to exception service
|
|
41
|
-
exceptionService.handleFatalError(error, {
|
|
42
|
-
componentStack: errorInfo.componentStack ?? undefined,
|
|
43
|
-
screen: 'ErrorBoundary',
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// Update state
|
|
47
|
-
setErrorState({
|
|
48
|
-
hasError: true,
|
|
49
|
-
error,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// Call external onError if provided
|
|
53
|
-
if (onError) {
|
|
54
|
-
onError(error, errorInfo);
|
|
55
|
-
}
|
|
56
|
-
}, [onError]);
|
|
57
|
-
|
|
58
|
-
// Reset error state
|
|
59
|
-
const resetError = useCallback(() => {
|
|
60
|
-
setErrorState({
|
|
61
|
-
hasError: false,
|
|
62
|
-
error: null,
|
|
63
|
-
});
|
|
64
|
-
}, []);
|
|
65
|
-
|
|
66
|
-
// If there's an error, render fallback
|
|
67
|
-
if (errorState.hasError) {
|
|
68
|
-
if (fallback) {
|
|
69
|
-
return <>{fallback}</>;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return (
|
|
73
|
-
<View style={[styles.container, { backgroundColor: tokens.colors.error.background }]}>
|
|
74
|
-
<View style={styles.content}>
|
|
75
|
-
<AtomicText variant="h3" style={styles.title}>
|
|
76
|
-
Something went wrong
|
|
77
|
-
</AtomicText>
|
|
78
|
-
|
|
79
|
-
{errorState.error && (
|
|
80
|
-
<AtomicText variant="body" style={styles.message}>
|
|
81
|
-
{errorState.error.message}
|
|
82
|
-
</AtomicText>
|
|
83
|
-
)}
|
|
84
|
-
|
|
85
|
-
<TouchableOpacity
|
|
86
|
-
style={[styles.button, { backgroundColor: tokens.colors.primary }]}
|
|
87
|
-
onPress={resetError}
|
|
88
|
-
>
|
|
89
|
-
<AtomicText variant="button" style={styles.buttonText}>
|
|
90
|
-
Try Again
|
|
91
|
-
</AtomicText>
|
|
92
|
-
</TouchableOpacity>
|
|
93
|
-
</View>
|
|
94
|
-
</View>
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return <>{children}</>;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const styles = StyleSheet.create({
|
|
102
|
-
container: {
|
|
103
|
-
flex: 1,
|
|
104
|
-
justifyContent: 'center',
|
|
105
|
-
alignItems: 'center',
|
|
106
|
-
padding: 20,
|
|
107
|
-
},
|
|
108
|
-
content: {
|
|
109
|
-
alignItems: 'center',
|
|
110
|
-
maxWidth: 400,
|
|
111
|
-
},
|
|
112
|
-
title: {
|
|
113
|
-
marginBottom: 10,
|
|
114
|
-
textAlign: 'center',
|
|
115
|
-
},
|
|
116
|
-
message: {
|
|
117
|
-
marginBottom: 20,
|
|
118
|
-
textAlign: 'center',
|
|
119
|
-
opacity: 0.7,
|
|
120
|
-
},
|
|
121
|
-
button: {
|
|
122
|
-
paddingHorizontal: 20,
|
|
123
|
-
paddingVertical: 12,
|
|
124
|
-
borderRadius: 8,
|
|
125
|
-
},
|
|
126
|
-
buttonText: {
|
|
127
|
-
color: '#fff',
|
|
128
|
-
},
|
|
129
|
-
});
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Empty State Component
|
|
3
|
-
* Displays when no data is available
|
|
4
|
-
*
|
|
5
|
-
* Presentation Layer - UI Component
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React, { useMemo } from "react";
|
|
9
|
-
import { View, StyleSheet, TouchableOpacity } from "react-native";
|
|
10
|
-
import { AtomicIcon, AtomicText } from "../../../atoms";
|
|
11
|
-
import { useAppDesignTokens } from "../../../theme";
|
|
12
|
-
|
|
13
|
-
export interface ExceptionEmptyStateProps {
|
|
14
|
-
icon?: string;
|
|
15
|
-
title: string;
|
|
16
|
-
description?: string;
|
|
17
|
-
actionLabel?: string;
|
|
18
|
-
onAction?: () => void;
|
|
19
|
-
illustration?: React.ReactNode;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const ExceptionEmptyState: React.FC<ExceptionEmptyStateProps> = ({
|
|
23
|
-
icon = "inbox",
|
|
24
|
-
title,
|
|
25
|
-
description,
|
|
26
|
-
actionLabel,
|
|
27
|
-
onAction,
|
|
28
|
-
illustration,
|
|
29
|
-
}) => {
|
|
30
|
-
const tokens = useAppDesignTokens();
|
|
31
|
-
|
|
32
|
-
const styles = useMemo(
|
|
33
|
-
() =>
|
|
34
|
-
StyleSheet.create({
|
|
35
|
-
actionButton: {
|
|
36
|
-
borderRadius: tokens.borders.radius.md,
|
|
37
|
-
marginTop: tokens.spacing.sm,
|
|
38
|
-
paddingHorizontal: tokens.spacing.lg,
|
|
39
|
-
paddingVertical: tokens.spacing.md,
|
|
40
|
-
},
|
|
41
|
-
actionButtonText: {
|
|
42
|
-
// AtomicText handles typography
|
|
43
|
-
},
|
|
44
|
-
container: {
|
|
45
|
-
alignItems: "center",
|
|
46
|
-
flex: 1,
|
|
47
|
-
justifyContent: "center",
|
|
48
|
-
padding: tokens.spacing.xl,
|
|
49
|
-
},
|
|
50
|
-
description: {
|
|
51
|
-
marginBottom: tokens.spacing.lg,
|
|
52
|
-
maxWidth: 280,
|
|
53
|
-
},
|
|
54
|
-
iconContainer: {
|
|
55
|
-
alignItems: "center",
|
|
56
|
-
borderRadius: 60,
|
|
57
|
-
height: 120,
|
|
58
|
-
justifyContent: "center",
|
|
59
|
-
marginBottom: tokens.spacing.lg,
|
|
60
|
-
width: 120,
|
|
61
|
-
},
|
|
62
|
-
title: {
|
|
63
|
-
marginBottom: tokens.spacing.sm,
|
|
64
|
-
},
|
|
65
|
-
}),
|
|
66
|
-
[tokens],
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
return (
|
|
70
|
-
<View style={styles.container}>
|
|
71
|
-
{illustration ? (
|
|
72
|
-
illustration
|
|
73
|
-
) : (
|
|
74
|
-
<View
|
|
75
|
-
style={[
|
|
76
|
-
styles.iconContainer,
|
|
77
|
-
{ backgroundColor: tokens.colors.surface },
|
|
78
|
-
]}
|
|
79
|
-
>
|
|
80
|
-
<AtomicIcon name={icon} size="xxl" color="secondary" />
|
|
81
|
-
</View>
|
|
82
|
-
)}
|
|
83
|
-
|
|
84
|
-
<AtomicText
|
|
85
|
-
type="headlineSmall"
|
|
86
|
-
color="primary"
|
|
87
|
-
style={[styles.title, { textAlign: "center" }]}
|
|
88
|
-
>
|
|
89
|
-
{title}
|
|
90
|
-
</AtomicText>
|
|
91
|
-
|
|
92
|
-
{description && (
|
|
93
|
-
<AtomicText
|
|
94
|
-
type="bodyMedium"
|
|
95
|
-
color="secondary"
|
|
96
|
-
style={[styles.description, { textAlign: "center" }]}
|
|
97
|
-
>
|
|
98
|
-
{description}
|
|
99
|
-
</AtomicText>
|
|
100
|
-
)}
|
|
101
|
-
|
|
102
|
-
{actionLabel && onAction && (
|
|
103
|
-
<TouchableOpacity
|
|
104
|
-
style={[
|
|
105
|
-
styles.actionButton,
|
|
106
|
-
{ backgroundColor: tokens.colors.primary },
|
|
107
|
-
]}
|
|
108
|
-
onPress={onAction}
|
|
109
|
-
activeOpacity={0.8}
|
|
110
|
-
>
|
|
111
|
-
<AtomicText
|
|
112
|
-
type="labelLarge"
|
|
113
|
-
color="onPrimary"
|
|
114
|
-
style={styles.actionButtonText}
|
|
115
|
-
>
|
|
116
|
-
{actionLabel}
|
|
117
|
-
</AtomicText>
|
|
118
|
-
</TouchableOpacity>
|
|
119
|
-
)}
|
|
120
|
-
</View>
|
|
121
|
-
);
|
|
122
|
-
};
|
|
123
|
-
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Error State Component
|
|
3
|
-
* Generic error display with retry action
|
|
4
|
-
*
|
|
5
|
-
* Presentation Layer - UI Component
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React, { useMemo } from "react";
|
|
9
|
-
import { View, StyleSheet, TouchableOpacity } from "react-native";
|
|
10
|
-
import {
|
|
11
|
-
AtomicIcon,
|
|
12
|
-
AtomicText,
|
|
13
|
-
useIconName,
|
|
14
|
-
} from "../../../atoms";
|
|
15
|
-
import {
|
|
16
|
-
useAppDesignTokens,
|
|
17
|
-
} from "../../../theme";
|
|
18
|
-
|
|
19
|
-
export interface ExceptionErrorStateProps {
|
|
20
|
-
/** Icon name (interpreted by app's icon renderer) */
|
|
21
|
-
icon?: string;
|
|
22
|
-
/** Error title */
|
|
23
|
-
title: string;
|
|
24
|
-
/** Error description */
|
|
25
|
-
description?: string;
|
|
26
|
-
/** Retry button label */
|
|
27
|
-
actionLabel?: string;
|
|
28
|
-
/** Retry action callback */
|
|
29
|
-
onAction?: () => void;
|
|
30
|
-
/** Custom illustration instead of icon */
|
|
31
|
-
illustration?: React.ReactNode;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export const ExceptionErrorState: React.FC<ExceptionErrorStateProps> = ({
|
|
35
|
-
icon,
|
|
36
|
-
title,
|
|
37
|
-
description,
|
|
38
|
-
actionLabel,
|
|
39
|
-
onAction,
|
|
40
|
-
illustration,
|
|
41
|
-
}) => {
|
|
42
|
-
const tokens = useAppDesignTokens();
|
|
43
|
-
const alertCircleIcon = useIconName('alertCircle');
|
|
44
|
-
const refreshIcon = useIconName('refresh');
|
|
45
|
-
const displayIcon = icon || alertCircleIcon;
|
|
46
|
-
|
|
47
|
-
const styles = useMemo(
|
|
48
|
-
() =>
|
|
49
|
-
StyleSheet.create({
|
|
50
|
-
actionButton: {
|
|
51
|
-
alignItems: "center",
|
|
52
|
-
borderRadius: tokens.borders.radius.full,
|
|
53
|
-
flexDirection: "row",
|
|
54
|
-
gap: tokens.spacing.sm,
|
|
55
|
-
marginTop: tokens.spacing.sm,
|
|
56
|
-
paddingHorizontal: tokens.spacing.lg,
|
|
57
|
-
paddingVertical: tokens.spacing.md,
|
|
58
|
-
},
|
|
59
|
-
container: {
|
|
60
|
-
alignItems: "center",
|
|
61
|
-
flex: 1,
|
|
62
|
-
justifyContent: "center",
|
|
63
|
-
padding: tokens.spacing.xl,
|
|
64
|
-
},
|
|
65
|
-
description: {
|
|
66
|
-
marginBottom: tokens.spacing.lg,
|
|
67
|
-
maxWidth: 280,
|
|
68
|
-
textAlign: "center",
|
|
69
|
-
},
|
|
70
|
-
iconContainer: {
|
|
71
|
-
alignItems: "center",
|
|
72
|
-
borderRadius: 60,
|
|
73
|
-
height: 120,
|
|
74
|
-
justifyContent: "center",
|
|
75
|
-
marginBottom: tokens.spacing.lg,
|
|
76
|
-
width: 120,
|
|
77
|
-
},
|
|
78
|
-
title: {
|
|
79
|
-
marginBottom: tokens.spacing.sm,
|
|
80
|
-
textAlign: "center",
|
|
81
|
-
},
|
|
82
|
-
}),
|
|
83
|
-
[tokens],
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
return (
|
|
87
|
-
<View style={styles.container}>
|
|
88
|
-
{illustration ? (
|
|
89
|
-
illustration
|
|
90
|
-
) : (
|
|
91
|
-
<View
|
|
92
|
-
style={[styles.iconContainer, { backgroundColor: tokens.colors.surface }]}
|
|
93
|
-
>
|
|
94
|
-
<AtomicIcon name={displayIcon as never} size="xxl" color="secondary" />
|
|
95
|
-
</View>
|
|
96
|
-
)}
|
|
97
|
-
|
|
98
|
-
<AtomicText type="headlineSmall" color="primary" style={styles.title}>
|
|
99
|
-
{title}
|
|
100
|
-
</AtomicText>
|
|
101
|
-
|
|
102
|
-
{description && (
|
|
103
|
-
<AtomicText type="bodyMedium" color="secondary" style={styles.description}>
|
|
104
|
-
{description}
|
|
105
|
-
</AtomicText>
|
|
106
|
-
)}
|
|
107
|
-
|
|
108
|
-
{actionLabel && onAction && (
|
|
109
|
-
<TouchableOpacity
|
|
110
|
-
style={[styles.actionButton, { backgroundColor: tokens.colors.primary }]}
|
|
111
|
-
onPress={onAction}
|
|
112
|
-
activeOpacity={0.8}
|
|
113
|
-
>
|
|
114
|
-
<AtomicIcon name={refreshIcon} size="sm" color="onPrimary" />
|
|
115
|
-
<AtomicText type="labelLarge" color="onPrimary">
|
|
116
|
-
{actionLabel}
|
|
117
|
-
</AtomicText>
|
|
118
|
-
</TouchableOpacity>
|
|
119
|
-
)}
|
|
120
|
-
</View>
|
|
121
|
-
);
|
|
122
|
-
};
|