@umituz/react-native-design-system 2.6.107 → 2.6.110

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@umituz/react-native-design-system",
3
- "version": "2.6.107",
4
- "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive and safe area utilities",
3
+ "version": "2.6.110",
4
+ "description": "Universal design system for React Native apps - Consolidated package with atoms, molecules, organisms, theme, typography, responsive, safe area and exception utilities",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
7
7
  "exports": {
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Exception Entity - Domain Layer
3
+ * Pure business logic representation of errors and exceptions
4
+ */
5
+
6
+ import { generateUUID } from '@umituz/react-native-uuid';
7
+
8
+ export type ExceptionSeverity = 'fatal' | 'error' | 'warning' | 'info';
9
+ export type ExceptionCategory = 'network' | 'validation' | 'authentication' | 'authorization' | 'business-logic' | 'system' | 'storage' | 'unknown';
10
+
11
+ export interface ExceptionContext {
12
+ userId?: string;
13
+ screen?: string;
14
+ action?: string;
15
+ componentStack?: string;
16
+ metadata?: Record<string, unknown>;
17
+ }
18
+
19
+ export interface ExceptionEntity {
20
+ id: string;
21
+ message: string;
22
+ stackTrace?: string;
23
+ severity: ExceptionSeverity;
24
+ category: ExceptionCategory;
25
+ context: ExceptionContext;
26
+ timestamp: Date;
27
+ handled: boolean;
28
+ reported: boolean;
29
+ }
30
+
31
+ export interface ErrorLog {
32
+ id: string;
33
+ exceptionId: string;
34
+ userId?: string;
35
+ message: string;
36
+ stackTrace?: string;
37
+ severity: ExceptionSeverity;
38
+ category: ExceptionCategory;
39
+ context: ExceptionContext;
40
+ createdAt: Date;
41
+ }
42
+
43
+ /**
44
+ * Factory function to create an exception entity
45
+ */
46
+ export function createException(
47
+ error: Error,
48
+ severity: ExceptionSeverity = 'error',
49
+ category: ExceptionCategory = 'unknown',
50
+ context: ExceptionContext = {}
51
+ ): ExceptionEntity {
52
+ return {
53
+ id: generateUUID(),
54
+ message: error.message,
55
+ stackTrace: error.stack,
56
+ severity,
57
+ category,
58
+ context,
59
+ timestamp: new Date(),
60
+ handled: false,
61
+ reported: false,
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Factory function to create an error log
67
+ */
68
+ export function createErrorLog(
69
+ exception: ExceptionEntity
70
+ ): ErrorLog {
71
+ return {
72
+ id: generateUUID(),
73
+ exceptionId: exception.id,
74
+ userId: exception.context.userId,
75
+ message: exception.message,
76
+ stackTrace: exception.stackTrace,
77
+ severity: exception.severity,
78
+ category: exception.category,
79
+ context: exception.context,
80
+ createdAt: new Date(),
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Determine if exception should be reported
86
+ */
87
+ export function shouldReportException(exception: ExceptionEntity): boolean {
88
+ // Don't report warnings or info
89
+ if (exception.severity === 'warning' || exception.severity === 'info') {
90
+ return false;
91
+ }
92
+
93
+ // Don't report validation errors (user errors)
94
+ if (exception.category === 'validation') {
95
+ return false;
96
+ }
97
+
98
+ // Report everything else
99
+ return true;
100
+ }
101
+
102
+
103
+
104
+
105
+
106
+
107
+
108
+
109
+
110
+
111
+
112
+
113
+
114
+
115
+
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Exception Repository Interface
3
+ * Defines the contract for exception data persistence
4
+ */
5
+
6
+ import type { ExceptionEntity, ErrorLog } from '../entities/ExceptionEntity';
7
+
8
+ export interface ExceptionRepositoryError {
9
+ code: string;
10
+ message: string;
11
+ }
12
+
13
+ export type ExceptionResult<T> =
14
+ | { success: true; data: T }
15
+ | { success: false; error: ExceptionRepositoryError };
16
+
17
+ export interface IExceptionRepository {
18
+ /**
19
+ * Save an exception to storage
20
+ */
21
+ save(exception: ExceptionEntity): Promise<ExceptionResult<void>>;
22
+
23
+ /**
24
+ * Get all stored exceptions
25
+ */
26
+ getAll(): Promise<ExceptionResult<ExceptionEntity[]>>;
27
+
28
+ /**
29
+ * Clear all stored exceptions
30
+ */
31
+ clear(): Promise<ExceptionResult<void>>;
32
+
33
+ /**
34
+ * Log an error
35
+ */
36
+ log(errorLog: ErrorLog): Promise<ExceptionResult<void>>;
37
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @umituz/react-native-exception - Public API
3
+ *
4
+ * Exception handling and error tracking for React Native apps
5
+ *
6
+ * Usage:
7
+ * import { ErrorBoundary, exceptionService, useExceptionStore } from '@umituz/react-native-exception';
8
+ */
9
+
10
+ // =============================================================================
11
+ // DOMAIN LAYER EXPORTS
12
+ // =============================================================================
13
+
14
+ // Entities
15
+ export type {
16
+ ExceptionEntity,
17
+ ErrorLog,
18
+ ExceptionContext,
19
+ ExceptionSeverity,
20
+ ExceptionCategory,
21
+ } from './domain/entities/ExceptionEntity';
22
+ export {
23
+ createException,
24
+ createErrorLog,
25
+ shouldReportException,
26
+ } from './domain/entities/ExceptionEntity';
27
+
28
+ // Repositories
29
+ export type {
30
+ IExceptionRepository,
31
+ ExceptionRepositoryError,
32
+ ExceptionResult,
33
+ } from './domain/repositories/IExceptionRepository';
34
+
35
+ // =============================================================================
36
+ // INFRASTRUCTURE LAYER EXPORTS
37
+ // =============================================================================
38
+
39
+ // State Store (Zustand)
40
+ export { useExceptionStore, useExceptions } from './infrastructure/storage/ExceptionStore';
41
+
42
+ // Services
43
+ // Infrastructure Services
44
+ export { ExceptionService } from './infrastructure/services/ExceptionService';
45
+ export { ExceptionHandler } from './infrastructure/services/ExceptionHandler';
46
+ export { ExceptionReporter } from './infrastructure/services/ExceptionReporter';
47
+ export { ExceptionLogger } from './infrastructure/services/ExceptionLogger';
48
+
49
+ // =============================================================================
50
+ // PRESENTATION LAYER EXPORTS
51
+ // =============================================================================
52
+
53
+ // Components
54
+ export { ErrorBoundary } from './presentation/components/ErrorBoundary';
55
+ export { ExceptionEmptyState } from './presentation/components/ExceptionEmptyState';
56
+ export type { ExceptionEmptyStateProps } from './presentation/components/ExceptionEmptyState';
57
+ export { ExceptionErrorState } from './presentation/components/ExceptionErrorState';
58
+ export type { ExceptionErrorStateProps } from './presentation/components/ExceptionErrorState';
59
+
60
+
61
+
62
+
63
+
64
+
65
+
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Exception Handler Service
3
+ *
4
+ * Handles exception creation, validation, and basic processing.
5
+ *
6
+ * SOLID: Single Responsibility - Only exception handling
7
+ * DRY: Centralized exception processing logic
8
+ * KISS: Simple exception handling interface
9
+ */
10
+
11
+ import type {
12
+ ExceptionEntity,
13
+ ExceptionContext,
14
+ ExceptionSeverity,
15
+ ExceptionCategory,
16
+ } from '../../domain/entities/ExceptionEntity';
17
+ import {
18
+ createException,
19
+ shouldReportException,
20
+ } from '../../domain/entities/ExceptionEntity';
21
+
22
+ export class ExceptionHandler {
23
+ /**
24
+ * Create and validate an exception
25
+ */
26
+ static createException(
27
+ error: Error,
28
+ severity: ExceptionSeverity = 'error',
29
+ category: ExceptionCategory = 'unknown',
30
+ context: ExceptionContext = {},
31
+ ): ExceptionEntity {
32
+ return createException(error, severity, category, context);
33
+ }
34
+
35
+ /**
36
+ * Check if exception should be reported
37
+ */
38
+ static shouldReportException(exception: ExceptionEntity): boolean {
39
+ return shouldReportException(exception);
40
+ }
41
+
42
+ /**
43
+ * Validate exception data
44
+ */
45
+ static validateException(exception: ExceptionEntity): boolean {
46
+ return !!(
47
+ exception.id &&
48
+ exception.message &&
49
+ exception.timestamp &&
50
+ exception.severity &&
51
+ exception.category
52
+ );
53
+ }
54
+
55
+ /**
56
+ * Sanitize exception for logging
57
+ */
58
+ static sanitizeException(exception: ExceptionEntity): ExceptionEntity {
59
+ return {
60
+ ...exception,
61
+ message: this.sanitizeMessage(exception.message),
62
+ context: this.sanitizeContext(exception.context),
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Sanitize error message
68
+ */
69
+ private static sanitizeMessage(message: string): string {
70
+ // Remove sensitive information from error messages
71
+ return message
72
+ .replace(/password=[^&\s]*/gi, 'password=***')
73
+ .replace(/token=[^&\s]*/gi, 'token=***')
74
+ .replace(/key=[^&\s]*/gi, 'key=***');
75
+ }
76
+
77
+ /**
78
+ * Sanitize context object
79
+ */
80
+ private static sanitizeContext(context: ExceptionContext): ExceptionContext {
81
+ const sanitized: ExceptionContext = { ...context };
82
+
83
+ // Remove sensitive fields
84
+ const sensitiveFields = ['password', 'token', 'apiKey', 'secret'];
85
+ sensitiveFields.forEach(field => {
86
+ if (sanitized.metadata && sanitized.metadata[field]) {
87
+ sanitized.metadata = { ...sanitized.metadata, [field]: '***' };
88
+ }
89
+ });
90
+
91
+ return sanitized;
92
+ }
93
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Exception Logger Service
3
+ *
4
+ * Handles local logging and persistence of exceptions.
5
+ *
6
+ * SOLID: Single Responsibility - Only exception logging/persistence
7
+ * DRY: Centralized logging logic
8
+ * KISS: Simple logging interface
9
+ */
10
+
11
+ import type { ExceptionEntity } from '../../domain/entities/ExceptionEntity';
12
+ import { ExceptionHandler } from './ExceptionHandler';
13
+ import { storageRepository } from '@umituz/react-native-storage';
14
+
15
+ export class ExceptionLogger {
16
+ private static readonly STORAGE_KEY = '@exceptions';
17
+ private maxStoredExceptions = 100;
18
+
19
+ /**
20
+ * Log exception locally
21
+ */
22
+ async logException(exception: ExceptionEntity): Promise<void> {
23
+ try {
24
+ const sanitizedException = ExceptionHandler.sanitizeException(exception);
25
+ const existingExceptions = await this.getStoredExceptions();
26
+
27
+ // Add new exception
28
+ existingExceptions.unshift(sanitizedException);
29
+
30
+ // Limit storage size
31
+ if (existingExceptions.length > this.maxStoredExceptions) {
32
+ existingExceptions.splice(this.maxStoredExceptions);
33
+ }
34
+
35
+ await storageRepository.setString(ExceptionLogger.STORAGE_KEY, JSON.stringify(existingExceptions));
36
+ } catch (error) {
37
+ // Fallback to console if storage fails
38
+ console.error('Failed to log exception:', error);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Get stored exceptions
44
+ */
45
+ async getStoredExceptions(): Promise<ExceptionEntity[]> {
46
+ try {
47
+ const result = await storageRepository.getString(ExceptionLogger.STORAGE_KEY, '[]');
48
+ if (result.success) {
49
+ return JSON.parse(result.data);
50
+ }
51
+ return [];
52
+ } catch (error) {
53
+ console.warn('Failed to get stored exceptions:', error);
54
+ return [];
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Clear all stored exceptions
60
+ */
61
+ async clearStoredExceptions(): Promise<void> {
62
+ try {
63
+ await storageRepository.setString(ExceptionLogger.STORAGE_KEY, '[]');
64
+ } catch (error) {
65
+ console.warn('Failed to clear stored exceptions:', error);
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Get exceptions by category
71
+ */
72
+ async getExceptionsByCategory(category: ExceptionEntity['category']): Promise<ExceptionEntity[]> {
73
+ const exceptions = await this.getStoredExceptions();
74
+ return exceptions.filter(ex => ex.category === category);
75
+ }
76
+
77
+ /**
78
+ * Get exceptions by severity
79
+ */
80
+ async getExceptionsBySeverity(severity: ExceptionEntity['severity']): Promise<ExceptionEntity[]> {
81
+ const exceptions = await this.getStoredExceptions();
82
+ return exceptions.filter(ex => ex.severity === severity);
83
+ }
84
+
85
+ /**
86
+ * Get recent exceptions (last N days)
87
+ */
88
+ async getRecentExceptions(days: number = 7): Promise<ExceptionEntity[]> {
89
+ const exceptions = await this.getStoredExceptions();
90
+ const cutoffDate = new Date();
91
+ cutoffDate.setDate(cutoffDate.getDate() - days);
92
+
93
+ return exceptions.filter(ex => new Date(ex.timestamp) >= cutoffDate);
94
+ }
95
+
96
+ /**
97
+ * Get exception statistics
98
+ */
99
+ async getExceptionStats(): Promise<{
100
+ total: number;
101
+ bySeverity: Record<ExceptionEntity['severity'], number>;
102
+ byCategory: Record<ExceptionEntity['category'], number>;
103
+ }> {
104
+ const exceptions = await this.getStoredExceptions();
105
+
106
+ const bySeverity: Record<ExceptionEntity['severity'], number> = {
107
+ fatal: 0,
108
+ error: 0,
109
+ warning: 0,
110
+ info: 0,
111
+ };
112
+
113
+ const byCategory: Record<ExceptionEntity['category'], number> = {
114
+ network: 0,
115
+ validation: 0,
116
+ authentication: 0,
117
+ authorization: 0,
118
+ 'business-logic': 0,
119
+ system: 0,
120
+ storage: 0,
121
+ unknown: 0,
122
+ };
123
+
124
+ exceptions.forEach(ex => {
125
+ bySeverity[ex.severity]++;
126
+ byCategory[ex.category]++;
127
+ });
128
+
129
+ return {
130
+ total: exceptions.length,
131
+ bySeverity,
132
+ byCategory,
133
+ };
134
+ }
135
+
136
+ /**
137
+ * Update max stored exceptions limit
138
+ */
139
+ setMaxStoredExceptions(limit: number): void {
140
+ this.maxStoredExceptions = Math.max(1, limit);
141
+ }
142
+ }
@@ -0,0 +1,134 @@
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
+
@@ -0,0 +1,154 @@
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;
25
+ private reporter: ExceptionReporter;
26
+
27
+ constructor(reporterConfig?: ExceptionReporter['config']) {
28
+ this.logger = new ExceptionLogger();
29
+ this.reporter = new ExceptionReporter(
30
+ reporterConfig || {
31
+ enabled: false,
32
+ environment: 'development'
33
+ }
34
+ );
35
+ }
36
+
37
+ /**
38
+ * Handle an exception
39
+ */
40
+ async handleException(
41
+ error: Error,
42
+ severity: ExceptionSeverity = 'error',
43
+ category: ExceptionCategory = 'unknown',
44
+ context: ExceptionContext = {},
45
+ ): Promise<void> {
46
+ const exception = ExceptionHandler.createException(error, severity, category, context);
47
+
48
+ // Add to store
49
+ useExceptionStore.getState().addException(exception);
50
+
51
+ // Log locally
52
+ await this.logger.logException(exception);
53
+
54
+ // Report to external service if needed
55
+ if (ExceptionHandler.shouldReportException(exception)) {
56
+ await this.reporter.reportException(exception);
57
+ }
58
+
59
+ // Mark as handled
60
+ useExceptionStore.getState().markAsHandled(exception.id);
61
+ }
62
+
63
+ /**
64
+ * Handle network errors
65
+ */
66
+ async handleNetworkError(error: Error, context: ExceptionContext = {}): Promise<void> {
67
+ await this.handleException(error, 'error', 'network', context);
68
+ }
69
+
70
+ /**
71
+ * Handle validation errors
72
+ */
73
+ async handleValidationError(error: Error, context: ExceptionContext = {}): Promise<void> {
74
+ await this.handleException(error, 'warning', 'validation', context);
75
+ }
76
+
77
+ /**
78
+ * Handle authentication errors
79
+ */
80
+ async handleAuthError(error: Error, context: ExceptionContext = {}): Promise<void> {
81
+ await this.handleException(error, 'error', 'authentication', context);
82
+ }
83
+
84
+ /**
85
+ * Handle system errors
86
+ */
87
+ async handleSystemError(error: Error, context: ExceptionContext = {}): Promise<void> {
88
+ await this.handleException(error, 'fatal', 'system', context);
89
+ }
90
+
91
+ /**
92
+ * Get stored exceptions
93
+ */
94
+ async getStoredExceptions(): Promise<ExceptionEntity[]> {
95
+ return this.logger.getStoredExceptions();
96
+ }
97
+
98
+ /**
99
+ * Get exception statistics
100
+ */
101
+ async getExceptionStats() {
102
+ return this.logger.getExceptionStats();
103
+ }
104
+
105
+ /**
106
+ * Clear stored exceptions
107
+ */
108
+ async clearStoredExceptions(): Promise<void> {
109
+ await this.logger.clearStoredExceptions();
110
+ }
111
+
112
+ /**
113
+ * Update reporter configuration
114
+ */
115
+ updateReporterConfig(config: Partial<ExceptionReporter['config']>): void {
116
+ this.reporter.updateConfig(config);
117
+ }
118
+
119
+ /**
120
+ * Set max stored exceptions
121
+ */
122
+ setMaxStoredExceptions(limit: number): void {
123
+ this.logger.setMaxStoredExceptions(limit);
124
+ }
125
+
126
+ /**
127
+ * Handle storage/permission errors
128
+ */
129
+ async handleStorageError(error: Error, context: ExceptionContext = {}): Promise<void> {
130
+ await this.handleException(error, 'error', 'storage', context);
131
+ }
132
+
133
+ /**
134
+ * Handle fatal errors
135
+ */
136
+ async handleFatalError(error: Error, context: ExceptionContext = {}): Promise<void> {
137
+ await this.handleException(error, 'fatal', 'system', context);
138
+ }
139
+
140
+ /**
141
+ * Clear all exceptions
142
+ */
143
+ clearExceptions(): void {
144
+ useExceptionStore.getState().clearExceptions();
145
+ }
146
+ }
147
+
148
+ // Export default instance
149
+ export const exceptionService = new ExceptionService();
150
+
151
+
152
+
153
+
154
+
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Exception Store
3
+ * Zustand store for exception state management
4
+ */
5
+
6
+ import { createStore } from '@umituz/react-native-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
+ };
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Error Boundary Component
3
+ * Catches React errors and provides fallback UI
4
+ */
5
+
6
+ import React, { Component, ReactNode } from 'react';
7
+ import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
8
+ import { exceptionService } from '../../infrastructure/services/ExceptionService';
9
+ import { useAppDesignTokens } from '../../../theme';
10
+
11
+ interface Props {
12
+ children: ReactNode;
13
+ fallback?: ReactNode;
14
+ onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
15
+ }
16
+
17
+ interface State {
18
+ hasError: boolean;
19
+ error: Error | null;
20
+ }
21
+
22
+ export class ErrorBoundary extends Component<Props, State> {
23
+ constructor(props: Props) {
24
+ super(props);
25
+ this.state = {
26
+ hasError: false,
27
+ error: null,
28
+ };
29
+ }
30
+
31
+ static getDerivedStateFromError(error: Error): State {
32
+ return {
33
+ hasError: true,
34
+ error,
35
+ };
36
+ }
37
+
38
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
39
+ // Log error to exception service
40
+ exceptionService.handleFatalError(error, {
41
+ componentStack: errorInfo.componentStack ?? undefined,
42
+ screen: 'ErrorBoundary',
43
+ });
44
+
45
+ // Call external onError if provided
46
+ if (this.props.onError) {
47
+ this.props.onError(error, errorInfo);
48
+ }
49
+ }
50
+
51
+ handleReset = (): void => {
52
+ this.setState({
53
+ hasError: false,
54
+ error: null,
55
+ });
56
+ };
57
+
58
+ render(): ReactNode {
59
+ if (this.state.hasError) {
60
+ if (this.props.fallback) {
61
+ return this.props.fallback;
62
+ }
63
+
64
+ return (
65
+ <ErrorDisplay error={this.state.error} onReset={this.handleReset} />
66
+ );
67
+ }
68
+
69
+ return this.props.children;
70
+ }
71
+ }
72
+
73
+ interface ErrorDisplayProps {
74
+ error: Error | null;
75
+ onReset: () => void;
76
+ }
77
+
78
+ const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ error, onReset }) => {
79
+ const tokens = useAppDesignTokens();
80
+ const styles = getStyles(tokens);
81
+
82
+ return (
83
+ <View style={styles.container}>
84
+ <Text style={styles.title}>Something went wrong</Text>
85
+ <Text style={styles.message}>
86
+ {error?.message || 'An unexpected error occurred'}
87
+ </Text>
88
+ <TouchableOpacity style={styles.button} onPress={onReset}>
89
+ <Text style={styles.buttonText}>Try Again</Text>
90
+ </TouchableOpacity>
91
+ </View>
92
+ );
93
+ };
94
+
95
+ const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
96
+ StyleSheet.create({
97
+ button: {
98
+ backgroundColor: tokens.colors.primary,
99
+ borderRadius: tokens.borders.radius.sm,
100
+ paddingHorizontal: tokens.spacing.lg,
101
+ paddingVertical: tokens.spacing.sm,
102
+ },
103
+ buttonText: {
104
+ color: tokens.colors.textInverse,
105
+ fontSize: tokens.typography.bodyLarge.fontSize,
106
+ fontWeight: tokens.typography.labelLarge.fontWeight,
107
+ },
108
+ container: {
109
+ alignItems: 'center',
110
+ backgroundColor: tokens.colors.backgroundPrimary,
111
+ flex: 1,
112
+ justifyContent: 'center',
113
+ padding: tokens.spacing.lg,
114
+ },
115
+ message: {
116
+ color: tokens.colors.textSecondary,
117
+ fontSize: tokens.typography.bodyLarge.fontSize,
118
+ marginBottom: tokens.spacing.lg,
119
+ textAlign: 'center',
120
+ },
121
+ title: {
122
+ color: tokens.colors.textPrimary,
123
+ fontSize: tokens.typography.headlineSmall.fontSize,
124
+ fontWeight: tokens.typography.headlineSmall.fontWeight,
125
+ marginBottom: tokens.spacing.sm,
126
+ },
127
+ });
128
+
129
+
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Empty State Component
3
+ * Displays when no data is available
4
+ *
5
+ * Presentation Layer - UI Component
6
+ */
7
+
8
+ import React 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 = React.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
+
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Error State Component
3
+ * Generic error display with retry action
4
+ *
5
+ * Presentation Layer - UI Component
6
+ */
7
+
8
+ import React from "react";
9
+ import { View, StyleSheet, TouchableOpacity } from "react-native";
10
+ import {
11
+ AtomicIcon,
12
+ AtomicText,
13
+ } from "../../../atoms";
14
+ import {
15
+ useAppDesignTokens,
16
+ } from "../../../theme";
17
+
18
+ export interface ExceptionErrorStateProps {
19
+ /** Icon name from Ionicons */
20
+ icon?: string;
21
+ /** Error title */
22
+ title: string;
23
+ /** Error description */
24
+ description?: string;
25
+ /** Retry button label */
26
+ actionLabel?: string;
27
+ /** Retry action callback */
28
+ onAction?: () => void;
29
+ /** Custom illustration instead of icon */
30
+ illustration?: React.ReactNode;
31
+ }
32
+
33
+ export const ExceptionErrorState: React.FC<ExceptionErrorStateProps> = ({
34
+ icon = "alert-circle-outline",
35
+ title,
36
+ description,
37
+ actionLabel,
38
+ onAction,
39
+ illustration,
40
+ }) => {
41
+ const tokens = useAppDesignTokens();
42
+
43
+ const styles = React.useMemo(
44
+ () =>
45
+ StyleSheet.create({
46
+ actionButton: {
47
+ alignItems: "center",
48
+ borderRadius: tokens.borders.radius.full,
49
+ flexDirection: "row",
50
+ gap: tokens.spacing.sm,
51
+ marginTop: tokens.spacing.sm,
52
+ paddingHorizontal: tokens.spacing.lg,
53
+ paddingVertical: tokens.spacing.md,
54
+ },
55
+ container: {
56
+ alignItems: "center",
57
+ flex: 1,
58
+ justifyContent: "center",
59
+ padding: tokens.spacing.xl,
60
+ },
61
+ description: {
62
+ marginBottom: tokens.spacing.lg,
63
+ maxWidth: 280,
64
+ textAlign: "center",
65
+ },
66
+ iconContainer: {
67
+ alignItems: "center",
68
+ borderRadius: 60,
69
+ height: 120,
70
+ justifyContent: "center",
71
+ marginBottom: tokens.spacing.lg,
72
+ width: 120,
73
+ },
74
+ title: {
75
+ marginBottom: tokens.spacing.sm,
76
+ textAlign: "center",
77
+ },
78
+ }),
79
+ [tokens],
80
+ );
81
+
82
+ return (
83
+ <View style={styles.container}>
84
+ {illustration ? (
85
+ illustration
86
+ ) : (
87
+ <View
88
+ style={[styles.iconContainer, { backgroundColor: tokens.colors.surface }]}
89
+ >
90
+ <AtomicIcon name={icon as never} size="xxl" color="secondary" />
91
+ </View>
92
+ )}
93
+
94
+ <AtomicText type="headlineSmall" color="primary" style={styles.title}>
95
+ {title}
96
+ </AtomicText>
97
+
98
+ {description && (
99
+ <AtomicText type="bodyMedium" color="secondary" style={styles.description}>
100
+ {description}
101
+ </AtomicText>
102
+ )}
103
+
104
+ {actionLabel && onAction && (
105
+ <TouchableOpacity
106
+ style={[styles.actionButton, { backgroundColor: tokens.colors.primary }]}
107
+ onPress={onAction}
108
+ activeOpacity={0.8}
109
+ >
110
+ <AtomicIcon name="refresh-outline" size="sm" color="onPrimary" />
111
+ <AtomicText type="labelLarge" color="onPrimary">
112
+ {actionLabel}
113
+ </AtomicText>
114
+ </TouchableOpacity>
115
+ )}
116
+ </View>
117
+ );
118
+ };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Exception Exports
3
+ *
4
+ * Exception handling and error tracking for React Native apps
5
+ */
6
+
7
+ export * from '../exception';
package/src/index.ts CHANGED
@@ -57,6 +57,11 @@ export * from './exports/organisms';
57
57
  // =============================================================================
58
58
  export * from './exports/safe-area';
59
59
 
60
+ // =============================================================================
61
+ // EXCEPTION EXPORTS
62
+ // =============================================================================
63
+ export * from './exports/exception';
64
+
60
65
  // =============================================================================
61
66
  // VARIANT UTILITIES
62
67
  // =============================================================================