@umituz/react-native-settings 5.4.9 → 5.4.11

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/core/base/BaseService.ts +141 -0
  3. package/src/core/index.ts +60 -0
  4. package/src/core/patterns/Modal/ModalConfig.ts +282 -0
  5. package/src/core/patterns/Modal/useModalState.ts +128 -0
  6. package/src/core/patterns/Screen/ScreenConfig.ts +375 -0
  7. package/src/core/patterns/Screen/useScreenData.ts +201 -0
  8. package/src/core/utils/logger.ts +138 -0
  9. package/src/core/utils/validators.ts +203 -0
  10. package/src/domains/disclaimer/index.ts +0 -3
  11. package/src/domains/disclaimer/presentation/components/DisclaimerSetting.tsx +18 -43
  12. package/src/domains/disclaimer/presentation/screens/DisclaimerScreen.tsx +42 -92
  13. package/src/domains/feedback/index.ts +2 -1
  14. package/src/domains/feedback/presentation/components/SupportSection.tsx +16 -43
  15. package/src/domains/feedback/presentation/screens/FeatureRequestScreen.tsx +4 -4
  16. package/src/domains/feedback/presentation/screens/FeedbackScreen.tsx +75 -0
  17. package/src/domains/notifications/infrastructure/services/NotificationService.ts +16 -13
  18. package/src/domains/rating/application/services/RatingService.ts +115 -79
  19. package/src/domains/rating/index.ts +3 -3
  20. package/src/domains/rating/presentation/hooks/useAppRating.tsx +42 -65
  21. package/src/domains/rating/presentation/screens/RatingPromptScreen.tsx +162 -0
  22. package/src/index.ts +12 -0
  23. package/src/infrastructure/services/SettingsService.ts +23 -19
  24. package/src/presentation/components/GenericModal.tsx +208 -0
  25. package/src/presentation/components/GenericScreen.tsx +273 -0
  26. package/src/presentation/components/index.ts +27 -0
  27. package/src/presentation/navigation/hooks/useSettingsScreens.ts +26 -1
  28. package/src/presentation/navigation/types.ts +6 -0
  29. package/src/domains/disclaimer/presentation/components/DisclaimerModal.tsx +0 -103
  30. package/src/domains/feedback/presentation/components/FeedbackModal.tsx +0 -99
  31. package/src/domains/rating/presentation/components/RatingPromptModal.tsx +0 -152
@@ -1,14 +1,15 @@
1
1
  /**
2
- * NotificationService
2
+ * Notification Service
3
3
  *
4
4
  * Simple facade for offline notification system.
5
5
  * Works in ALL apps - offline, online, hybrid - no backend required.
6
+ * Refactored to extend BaseService.
6
7
  *
7
8
  * @module NotificationService
8
9
  */
9
10
 
10
11
  import { NotificationManager } from './NotificationManager';
11
- import { isDev } from '../../../../utils/devUtils';
12
+ import { BaseService } from '../../../../core/base/BaseService';
12
13
 
13
14
  export * from './types';
14
15
 
@@ -16,26 +17,22 @@ export * from './types';
16
17
  * Notification service singleton
17
18
  * Provides simple access to notification manager
18
19
  */
19
- export class NotificationService {
20
+ export class NotificationService extends BaseService {
21
+ protected serviceName = 'NotificationService';
20
22
  private static instance: NotificationService | null = null;
21
23
  private isConfigured = false;
22
24
 
23
25
  readonly notifications = new NotificationManager();
24
26
 
25
27
  private constructor() {
28
+ super();
26
29
  // Configuration deferred to first use
27
30
  }
28
31
 
29
32
  private ensureConfigured() {
30
33
  if (!this.isConfigured) {
31
- try {
32
- NotificationManager.configure();
33
- this.isConfigured = true;
34
- } catch (error) {
35
- if (isDev()) {
36
- console.error('[NotificationService] Failed to configure NotificationManager:', error);
37
- }
38
- }
34
+ NotificationManager.configure();
35
+ this.isConfigured = true;
39
36
  }
40
37
  }
41
38
 
@@ -51,7 +48,10 @@ export class NotificationService {
51
48
  */
52
49
  async requestPermissions(): Promise<boolean> {
53
50
  this.ensureConfigured();
54
- return await this.notifications.requestPermissions();
51
+ const result = await this.execute('requestPermissions', async () => {
52
+ return await this.notifications.requestPermissions();
53
+ });
54
+ return result.success ? result.data : false;
55
55
  }
56
56
 
57
57
  /**
@@ -59,7 +59,10 @@ export class NotificationService {
59
59
  */
60
60
  async hasPermissions(): Promise<boolean> {
61
61
  this.ensureConfigured();
62
- return await this.notifications.hasPermissions();
62
+ const result = await this.execute('hasPermissions', async () => {
63
+ return await this.notifications.hasPermissions();
64
+ });
65
+ return result.success ? result.data : false;
63
66
  }
64
67
  }
65
68
 
@@ -1,10 +1,14 @@
1
1
  /**
2
2
  * Rating Service
3
- * Core business logic for app rating system
3
+ *
4
+ * Core business logic for app rating system.
5
+ * Refactored to extend BaseService for consistent error handling.
6
+ *
7
+ * @module RatingService
4
8
  */
5
9
 
6
10
  import type { RatingConfig, RatingState } from "../../domain/entities/RatingConfig";
7
- import { isDev } from "../../../../utils/devUtils";
11
+ import { BaseService } from "../../../../core/base/BaseService";
8
12
  import {
9
13
  getEventCount,
10
14
  incrementEventCount,
@@ -35,112 +39,144 @@ function toISOString(date: Date = new Date()): string {
35
39
  }
36
40
 
37
41
  /**
38
- * Track an event occurrence
42
+ * Rating Service Class
39
43
  */
40
- export async function trackEvent(eventType: string): Promise<void> {
41
- try {
42
- await incrementEventCount(eventType);
43
- } catch (error) {
44
- if (isDev()) {
45
- console.error('[RatingService] Failed to track event:', eventType, error);
46
- }
44
+ export class RatingService extends BaseService {
45
+ protected serviceName = "RatingService";
46
+
47
+ /**
48
+ * Track an event occurrence
49
+ */
50
+ async trackEvent(eventType: string): Promise<void> {
51
+ await this.execute("trackEvent", async () => {
52
+ await incrementEventCount(eventType);
53
+ });
47
54
  }
48
- }
49
55
 
50
- /**
51
- * Check if prompt should be shown based on criteria
52
- */
53
- export async function shouldShowPrompt(config: RatingConfig): Promise<boolean> {
54
- try {
55
- const hasRated = await getHasRated();
56
- if (hasRated) {
57
- return false;
58
- }
56
+ /**
57
+ * Check if prompt should be shown based on criteria
58
+ */
59
+ async shouldShowPrompt(config: RatingConfig): Promise<boolean> {
60
+ const result = await this.execute("shouldShowPrompt", async () => {
61
+ const hasRated = await getHasRated();
62
+ if (hasRated) return false;
59
63
 
60
- const dismissed = await getDismissed();
61
- if (dismissed) {
62
- return false;
63
- }
64
+ const dismissed = await getDismissed();
65
+ if (dismissed) return false;
64
66
 
65
- const eventCount = await getEventCount(config.eventType);
66
- const minCount = config.minEventCount ?? 3;
67
+ const eventCount = await getEventCount(config.eventType);
68
+ const minCount = config.minEventCount ?? 3;
67
69
 
68
- if (eventCount < minCount) {
69
- return false;
70
- }
70
+ if (eventCount < minCount) return false;
71
71
 
72
- const lastPromptDate = await getLastPromptDate(config.eventType);
72
+ const lastPromptDate = await getLastPromptDate(config.eventType);
73
73
 
74
- if (lastPromptDate) {
75
- const cooldownDays = config.cooldownDays ?? 90;
76
- const daysSinceLastPrompt = daysBetween(lastPromptDate, new Date());
74
+ if (lastPromptDate) {
75
+ const cooldownDays = config.cooldownDays ?? 90;
76
+ const daysSinceLastPrompt = daysBetween(lastPromptDate, new Date());
77
77
 
78
- if (daysSinceLastPrompt < cooldownDays) {
79
- return false;
78
+ if (daysSinceLastPrompt < cooldownDays) {
79
+ return false;
80
+ }
80
81
  }
81
- }
82
82
 
83
- return true;
84
- } catch (error) {
85
- return false;
83
+ return true;
84
+ });
85
+
86
+ return result.success ? result.data : false;
87
+ }
88
+
89
+ /**
90
+ * Mark that prompt was shown to user
91
+ */
92
+ async markPromptShown(eventType: string): Promise<void> {
93
+ await this.execute("markPromptShown", async () => {
94
+ await setLastPromptDate(eventType, toISOString());
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Mark that user has rated the app
100
+ */
101
+ async markRated(): Promise<void> {
102
+ await this.execute("markRated", async () => {
103
+ await setHasRated(true);
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Mark that user permanently dismissed the prompt
109
+ */
110
+ async markDismissed(): Promise<void> {
111
+ await this.execute("markDismissed", async () => {
112
+ await setDismissed(true);
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Get current rating state for event type
118
+ */
119
+ async getState(eventType: string): Promise<RatingState> {
120
+ return getRatingState(eventType);
121
+ }
122
+
123
+ /**
124
+ * Reset rating data (for testing or specific event type)
125
+ */
126
+ async reset(eventType?: string): Promise<void> {
127
+ await this.execute("reset", async () => {
128
+ await resetStorage(eventType);
129
+ });
86
130
  }
87
131
  }
88
132
 
89
133
  /**
90
- * Mark that prompt was shown to user
134
+ * Singleton instance
91
135
  */
92
- export async function markPromptShown(eventType: string): Promise<void> {
93
- try {
94
- await setLastPromptDate(eventType, toISOString());
95
- } catch (error) {
96
- if (isDev()) {
97
- console.error('[RatingService] Failed to mark prompt shown:', eventType, error);
98
- }
99
- }
100
- }
136
+ let ratingServiceInstance: RatingService | null = null;
101
137
 
102
138
  /**
103
- * Mark that user has rated the app
139
+ * Get rating service singleton instance
104
140
  */
105
- export async function markRated(): Promise<void> {
106
- try {
107
- await setHasRated(true);
108
- } catch (error) {
109
- if (isDev()) {
110
- console.error('[RatingService] Failed to mark as rated:', error);
111
- }
141
+ export function getRatingService(): RatingService {
142
+ if (!ratingServiceInstance) {
143
+ ratingServiceInstance = new RatingService();
112
144
  }
145
+ return ratingServiceInstance;
113
146
  }
114
147
 
115
148
  /**
116
- * Mark that user permanently dismissed the prompt
149
+ * Default export for backward compatibility
117
150
  */
118
- export async function markDismissed(): Promise<void> {
119
- try {
120
- await setDismissed(true);
121
- } catch (error) {
122
- if (isDev()) {
123
- console.error('[RatingService] Failed to mark as dismissed:', error);
124
- }
125
- }
126
- }
151
+ export const ratingService = getRatingService();
127
152
 
128
153
  /**
129
- * Get current rating state for event type
154
+ * Static convenience functions that delegate to singleton
130
155
  */
156
+ export async function trackEvent(eventType: string): Promise<void> {
157
+ await ratingService.trackEvent(eventType);
158
+ }
159
+
160
+ export async function shouldShowPrompt(config: RatingConfig): Promise<boolean> {
161
+ return await ratingService.shouldShowPrompt(config);
162
+ }
163
+
164
+ export async function markPromptShown(eventType: string): Promise<void> {
165
+ await ratingService.markPromptShown(eventType);
166
+ }
167
+
168
+ export async function markRated(): Promise<void> {
169
+ await ratingService.markRated();
170
+ }
171
+
172
+ export async function markDismissed(): Promise<void> {
173
+ await ratingService.markDismissed();
174
+ }
175
+
131
176
  export async function getState(eventType: string): Promise<RatingState> {
132
- return getRatingState(eventType);
177
+ return await ratingService.getState(eventType);
133
178
  }
134
179
 
135
- /**
136
- * Reset rating data (for testing or specific event type)
137
- */
138
180
  export async function reset(eventType?: string): Promise<void> {
139
- try {
140
- await resetStorage(eventType);
141
- } catch (error) {
142
- if (isDev()) {
143
- console.error('[RatingService] Failed to reset:', eventType, error);
144
- }
145
- }
181
+ await ratingService.reset(eventType);
146
182
  }
@@ -34,11 +34,11 @@ export { StarRating } from './presentation/components/StarRating';
34
34
  export type { StarRatingProps } from './presentation/components/StarRating';
35
35
 
36
36
  // =============================================================================
37
- // PRESENTATION LAYER - Components (App Store Rating)
37
+ // PRESENTATION LAYER - Screens
38
38
  // =============================================================================
39
39
 
40
- export { RatingPromptModal } from './presentation/components/RatingPromptModal';
41
- export type { RatingPromptModalProps } from './presentation/components/RatingPromptModal';
40
+ export { RatingPromptScreen } from './presentation/screens/RatingPromptScreen';
41
+ export type { RatingPromptScreenProps, RatingPromptScreenParams } from './presentation/screens/RatingPromptScreen';
42
42
 
43
43
  // =============================================================================
44
44
  // PRESENTATION LAYER - Hooks (App Store Rating)
@@ -4,7 +4,7 @@
4
4
  * Lazy loads expo-store-review to reduce bundle size
5
5
  */
6
6
 
7
- import React, { useState, useCallback, useMemo } from "react";
7
+ import React, { useCallback, useMemo } from "react";
8
8
  import type {
9
9
  RatingConfig,
10
10
  UseAppRatingResult,
@@ -12,14 +12,14 @@ import type {
12
12
  } from "../../domain/entities/RatingConfig";
13
13
  import { DEFAULT_RATING_CONFIG } from "../../domain/entities/RatingConfig";
14
14
  import * as RatingService from "../../application/services/RatingService";
15
- import { RatingPromptModal } from "../components/RatingPromptModal";
15
+ import { useAppNavigation } from "@umituz/react-native-design-system/molecules";
16
16
  import { isDev } from "../../../../utils/devUtils";
17
17
 
18
18
  /**
19
- * App rating hook with 2-step prompt flow
19
+ * App rating hook with navigation-based prompt flow
20
20
  */
21
21
  export function useAppRating(config: RatingConfig): UseAppRatingResult {
22
- const [isVisible, setIsVisible] = useState(false);
22
+ const navigation = useAppNavigation();
23
23
 
24
24
  const mergedConfig = useMemo<RatingConfig>(() => ({
25
25
  ...DEFAULT_RATING_CONFIG,
@@ -35,13 +35,48 @@ export function useAppRating(config: RatingConfig): UseAppRatingResult {
35
35
  }, [mergedConfig]);
36
36
 
37
37
  const showPrompt = useCallback(async (): Promise<void> => {
38
- setIsVisible(true);
39
38
  await RatingService.markPromptShown(mergedConfig.eventType);
40
39
 
40
+ navigation.push('RatingPrompt' as never, {
41
+ appName: mergedConfig.appName,
42
+ translations: mergedConfig.translations,
43
+ onPositive: async () => {
44
+ await RatingService.markRated();
45
+
46
+ try {
47
+ // Lazy load expo-store-review
48
+ const StoreReview = await import('expo-store-review');
49
+ const isAvailable = await StoreReview.isAvailableAsync();
50
+
51
+ if (isAvailable) {
52
+ await StoreReview.requestReview();
53
+ }
54
+ } catch (error) {
55
+ if (isDev()) {
56
+ console.error('[useAppRating] Failed to request review:', error);
57
+ }
58
+ }
59
+
60
+ if (mergedConfig.onPositiveFeedback) {
61
+ await mergedConfig.onPositiveFeedback();
62
+ }
63
+ },
64
+ onNegative: async () => {
65
+ if (mergedConfig.onNegativeFeedback) {
66
+ await mergedConfig.onNegativeFeedback();
67
+ }
68
+ },
69
+ onLater: async () => {
70
+ if (mergedConfig.onPromptDismissed) {
71
+ await mergedConfig.onPromptDismissed();
72
+ }
73
+ },
74
+ });
75
+
41
76
  if (mergedConfig.onPromptShown) {
42
77
  await mergedConfig.onPromptShown();
43
78
  }
44
- }, [mergedConfig]);
79
+ }, [mergedConfig, navigation]);
45
80
 
46
81
  const checkAndShow = useCallback(async (): Promise<boolean> => {
47
82
  const should = await shouldShow();
@@ -62,62 +97,6 @@ export function useAppRating(config: RatingConfig): UseAppRatingResult {
62
97
  return RatingService.getState(mergedConfig.eventType);
63
98
  }, [mergedConfig.eventType]);
64
99
 
65
- const handlePositive = useCallback(async () => {
66
- setIsVisible(false);
67
- await RatingService.markRated();
68
-
69
- try {
70
- // Lazy load expo-store-review
71
- const StoreReview = await import('expo-store-review');
72
- const isAvailable = await StoreReview.isAvailableAsync();
73
-
74
- if (isAvailable) {
75
- await StoreReview.requestReview();
76
- }
77
- } catch (error) {
78
- if (isDev()) {
79
- console.error('[useAppRating] Failed to request review:', error);
80
- }
81
- }
82
-
83
- if (mergedConfig.onPositiveFeedback) {
84
- await mergedConfig.onPositiveFeedback();
85
- }
86
- }, [mergedConfig]);
87
-
88
- const handleNegative = useCallback(async () => {
89
- setIsVisible(false);
90
-
91
- if (mergedConfig.onNegativeFeedback) {
92
- await mergedConfig.onNegativeFeedback();
93
- }
94
- }, [mergedConfig]);
95
-
96
- const handleLater = useCallback(() => {
97
- setIsVisible(false);
98
- }, []);
99
-
100
- const handleDismiss = useCallback(async () => {
101
- setIsVisible(false);
102
- await RatingService.markDismissed();
103
-
104
- if (mergedConfig.onPromptDismissed) {
105
- await mergedConfig.onPromptDismissed();
106
- }
107
- }, [mergedConfig]);
108
-
109
- const modal = (
110
- <RatingPromptModal
111
- visible={isVisible}
112
- onPositive={handlePositive}
113
- onNegative={handleNegative}
114
- onLater={handleLater}
115
- onDismiss={handleDismiss}
116
- translations={mergedConfig.translations}
117
- appName={mergedConfig.appName}
118
- />
119
- );
120
-
121
100
  return {
122
101
  trackEvent,
123
102
  checkAndShow,
@@ -125,7 +104,5 @@ export function useAppRating(config: RatingConfig): UseAppRatingResult {
125
104
  showPrompt,
126
105
  reset,
127
106
  getState,
128
- isVisible,
129
- modal,
130
- } as UseAppRatingResult & { modal: React.ReactNode };
107
+ } as UseAppRatingResult;
131
108
  }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Rating Prompt Screen
3
+ *
4
+ * Full screen for app rating prompt.
5
+ * Replaces modal approach with native navigation.
6
+ */
7
+
8
+ import React from 'react';
9
+ import { View, StyleSheet, ScrollView } from 'react-native';
10
+ import { ScreenLayout } from '@umituz/react-native-design-system/layouts';
11
+ import { AtomicText, AtomicButton, AtomicIcon } from '@umituz/react-native-design-system/atoms';
12
+ import { NavigationHeader, useAppNavigation } from '@umituz/react-native-design-system/molecules';
13
+ import { useAppDesignTokens } from '@umituz/react-native-design-system/theme';
14
+ import type { RatingTranslations } from '../../domain/entities/RatingConfig';
15
+
16
+ export interface RatingPromptScreenParams {
17
+ appName?: string;
18
+ translations?: RatingTranslations;
19
+ onPositive?: () => void;
20
+ onNegative?: () => void;
21
+ onLater?: () => void;
22
+ [key: string]: unknown;
23
+ }
24
+
25
+ export interface RatingPromptScreenProps {
26
+ route: {
27
+ params: RatingPromptScreenParams;
28
+ };
29
+ }
30
+
31
+ export const RatingPromptScreen: React.FC<RatingPromptScreenProps> = ({ route }) => {
32
+ const navigation = useAppNavigation();
33
+ const tokens = useAppDesignTokens();
34
+ const styles = getStyles(tokens);
35
+ const { appName = 'this app', translations, onPositive, onNegative, onLater } = route.params;
36
+
37
+ const defaultTranslations: RatingTranslations = {
38
+ title: translations?.title ?? 'Enjoying the app?',
39
+ message:
40
+ translations?.message ??
41
+ `If you love using ${appName}, would you mind taking a moment to rate it?`,
42
+ positiveButton: translations?.positiveButton ?? 'Yes, I love it!',
43
+ negativeButton: translations?.negativeButton ?? 'Not really',
44
+ laterButton: translations?.laterButton ?? 'Maybe later',
45
+ };
46
+
47
+ const handlePositive = async () => {
48
+ try {
49
+ // Lazy load expo-store-review
50
+ const StoreReview = await import('expo-store-review');
51
+ const isAvailable = await StoreReview.isAvailableAsync();
52
+
53
+ if (isAvailable) {
54
+ await StoreReview.requestReview();
55
+ }
56
+ } catch (error) {
57
+ console.error('[RatingPromptScreen] Failed to request review:', error);
58
+ }
59
+
60
+ if (onPositive) {
61
+ onPositive();
62
+ }
63
+ navigation.goBack();
64
+ };
65
+
66
+ const handleNegative = () => {
67
+ if (onNegative) {
68
+ onNegative();
69
+ }
70
+ navigation.goBack();
71
+ };
72
+
73
+ const handleLater = () => {
74
+ if (onLater) {
75
+ onLater();
76
+ }
77
+ navigation.goBack();
78
+ };
79
+
80
+ return (
81
+ <ScreenLayout
82
+ scrollable={true}
83
+ edges={['top', 'bottom', 'left', 'right']}
84
+ hideScrollIndicator={false}
85
+ >
86
+ <NavigationHeader title="" />
87
+ <ScrollView
88
+ style={styles.scrollView}
89
+ contentContainerStyle={styles.scrollContent}
90
+ >
91
+ <View style={styles.iconContainer}>
92
+ <AtomicIcon name="star" size="xxl" color="primary" />
93
+ </View>
94
+
95
+ <AtomicText style={styles.title}>{defaultTranslations.title}</AtomicText>
96
+
97
+ <AtomicText style={styles.message}>{defaultTranslations.message}</AtomicText>
98
+
99
+ <View style={styles.buttonContainer}>
100
+ <AtomicButton
101
+ variant="primary"
102
+ onPress={handlePositive}
103
+ style={styles.button}
104
+ >
105
+ {defaultTranslations.positiveButton}
106
+ </AtomicButton>
107
+
108
+ <AtomicButton
109
+ variant="outline"
110
+ onPress={handleNegative}
111
+ style={styles.button}
112
+ >
113
+ {defaultTranslations.negativeButton}
114
+ </AtomicButton>
115
+
116
+ <AtomicButton
117
+ variant="text"
118
+ onPress={handleLater}
119
+ style={styles.button}
120
+ >
121
+ {defaultTranslations.laterButton}
122
+ </AtomicButton>
123
+ </View>
124
+ </ScrollView>
125
+ </ScreenLayout>
126
+ );
127
+ };
128
+
129
+ const getStyles = (_tokens: ReturnType<typeof useAppDesignTokens>) =>
130
+ StyleSheet.create({
131
+ scrollView: {
132
+ flex: 1,
133
+ },
134
+ scrollContent: {
135
+ padding: 20,
136
+ alignItems: 'center',
137
+ justifyContent: 'center',
138
+ minHeight: '100%',
139
+ },
140
+ iconContainer: {
141
+ marginBottom: 24,
142
+ },
143
+ title: {
144
+ fontSize: 24,
145
+ fontWeight: '700',
146
+ textAlign: 'center',
147
+ marginBottom: 16,
148
+ },
149
+ message: {
150
+ fontSize: 16,
151
+ textAlign: 'center',
152
+ marginBottom: 32,
153
+ lineHeight: 24,
154
+ },
155
+ buttonContainer: {
156
+ width: '100%',
157
+ gap: 12,
158
+ },
159
+ button: {
160
+ width: '100%',
161
+ },
162
+ });
package/src/index.ts CHANGED
@@ -8,6 +8,18 @@
8
8
  * import { useSettings, SettingsScreen, AppearanceScreen, SettingsItemCard, DisclaimerSetting } from '@umituz/react-native-settings';
9
9
  */
10
10
 
11
+ // =============================================================================
12
+ // CORE LAYER - Base Classes & Patterns
13
+ // =============================================================================
14
+
15
+ export * from './core';
16
+
17
+ // =============================================================================
18
+ // PRESENTATION LAYER - Generic Components
19
+ // =============================================================================
20
+
21
+ export * from './presentation/components';
22
+
11
23
  // =============================================================================
12
24
  // DOMAIN LAYER - Repository Interfaces
13
25
  // =============================================================================