@umituz/react-native-settings 4.23.120 → 4.23.122

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 (38) hide show
  1. package/package.json +1 -1
  2. package/src/domains/about/presentation/components/AboutSection.tsx +3 -3
  3. package/src/domains/appearance/hooks/useAppearanceActions.ts +8 -9
  4. package/src/domains/appearance/presentation/components/AppearanceSection.tsx +3 -3
  5. package/src/domains/appearance/presentation/components/ColorPicker.tsx +3 -1
  6. package/src/domains/feedback/presentation/components/SupportSection.tsx +1 -0
  7. package/src/domains/feedback/presentation/hooks/useFeedbackForm.ts +11 -3
  8. package/src/domains/gamification/components/GamificationScreen/AchievementsList.tsx +19 -7
  9. package/src/domains/legal/presentation/components/LegalSection.tsx +3 -3
  10. package/src/domains/localization/infrastructure/components/useLanguageNavigation.ts +3 -3
  11. package/src/domains/localization/infrastructure/hooks/useLanguageSelection.ts +11 -10
  12. package/src/domains/localization/infrastructure/hooks/useLocalization.ts +8 -5
  13. package/src/domains/localization/presentation/components/LanguageSection.tsx +3 -3
  14. package/src/domains/localization/presentation/providers/LocalizationManager.tsx +3 -0
  15. package/src/domains/notifications/infrastructure/hooks/useNotificationSettings.ts +10 -3
  16. package/src/domains/notifications/presentation/components/NotificationsSection.tsx +3 -3
  17. package/src/domains/notifications/presentation/hooks/useTimePicker.ts +14 -4
  18. package/src/domains/notifications/reminders/presentation/screens/ReminderListScreen.tsx +19 -8
  19. package/src/domains/rating/presentation/hooks/useAppRating.tsx +1 -0
  20. package/src/infrastructure/utils/memoUtils.ts +4 -4
  21. package/src/presentation/components/ErrorBoundary/SettingsErrorBoundary.tsx +105 -0
  22. package/src/presentation/components/ErrorBoundary/index.ts +9 -0
  23. package/src/presentation/hooks/useSettingsScreenConfig.ts +2 -2
  24. package/src/presentation/navigation/hooks/useSettingsNavigation.ts +28 -0
  25. package/src/presentation/navigation/hooks/useSettingsScreens.ts +10 -4
  26. package/src/presentation/screens/components/GamificationSettingsItem.tsx +3 -3
  27. package/src/presentation/screens/components/SettingsContent.tsx +0 -1
  28. package/src/presentation/screens/components/SubscriptionSettingsItem.tsx +3 -3
  29. package/src/presentation/screens/components/VideoTutorialSettingsItem.tsx +3 -3
  30. package/src/presentation/screens/components/WalletSettingsItem.tsx +3 -3
  31. package/src/presentation/screens/components/sections/FeatureSettingsSection.tsx +3 -3
  32. package/src/presentation/screens/components/sections/SupportSettingsSection.tsx +3 -3
  33. package/src/presentation/utils/config-creators/feature-configs.ts +0 -1
  34. package/src/presentation/utils/config-creators/types.ts +6 -1
  35. package/src/presentation/utils/faqTranslator.ts +2 -1
  36. package/src/presentation/utils/screenFactory.ts +4 -4
  37. package/src/presentation/utils/useAuthHandlers.ts +40 -3
  38. package/src/utils/devUtils.ts +60 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-settings",
3
- "version": "4.23.120",
3
+ "version": "4.23.122",
4
4
  "description": "Complete settings hub for React Native apps - consolidated package with settings, localization, about, legal, appearance, feedback, FAQs, rating, and gamification",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
2
  import { ViewStyle } from 'react-native';
3
- import { useAppNavigation } from '@umituz/react-native-design-system';
4
3
  import { AboutConfig } from '../../domain/entities/AppInfo';
5
4
  import { SettingsItemCard } from '../../../../presentation/components/SettingsItemCard';
5
+ import { useSettingsNavigation } from '../../../../presentation/navigation/hooks/useSettingsNavigation';
6
6
 
7
7
  export interface AboutSectionProps {
8
8
  config?: AboutConfig;
@@ -27,7 +27,7 @@ export const AboutSection: React.FC<AboutSectionProps> = ({
27
27
  hideMargin,
28
28
  }) => {
29
29
 
30
- const navigation = useAppNavigation();
30
+ const navigation = useSettingsNavigation();
31
31
 
32
32
  const route = config?.route || config?.defaultRoute || 'About';
33
33
  const title = propsTitle || config?.title;
@@ -40,7 +40,7 @@ export const AboutSection: React.FC<AboutSectionProps> = ({
40
40
  } else if (config?.onPress) {
41
41
  config.onPress();
42
42
  } else {
43
- navigation.navigate(route as never);
43
+ navigation.navigate(route as 'About');
44
44
  }
45
45
  };
46
46
 
@@ -29,15 +29,14 @@ export const useAppearanceActions = () => {
29
29
  }, [setThemeMode]);
30
30
 
31
31
  const handleColorChange = useCallback((key: keyof CustomThemeColors, color: string) => {
32
- // Update local state immediately for UI responsiveness
33
- const newColors = { ...localCustomColors, [key]: color };
34
-
35
- // Batch state updates to prevent race conditions
36
- setLocalCustomColors(newColors);
37
-
38
- // Update global state separately
39
- setCustomColors(newColors);
40
- }, [localCustomColors, setCustomColors]);
32
+ // Use functional update to avoid stale closure
33
+ setLocalCustomColors(prev => {
34
+ const newColors = { ...prev, [key]: color };
35
+ // Update global state with new colors
36
+ setCustomColors(newColors);
37
+ return newColors;
38
+ });
39
+ }, [setCustomColors]);
41
40
 
42
41
  const handleResetColors = useCallback(() => {
43
42
  reset();
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
2
  import { ViewStyle } from 'react-native';
3
- import { useAppNavigation } from '@umituz/react-native-design-system';
4
3
  import { AppearanceSectionConfig } from '../../types';
5
4
  import { SettingsItemCard } from '../../../../presentation/components/SettingsItemCard';
5
+ import { useSettingsNavigation } from '../../../../presentation/navigation/hooks/useSettingsNavigation';
6
6
 
7
7
  export interface AppearanceSectionProps {
8
8
  config?: AppearanceSectionConfig;
@@ -29,7 +29,7 @@ export const AppearanceSection: React.FC<AppearanceSectionProps> = ({
29
29
  hideMargin,
30
30
  }) => {
31
31
 
32
- const navigation = useAppNavigation();
32
+ const navigation = useSettingsNavigation();
33
33
 
34
34
  const route = config?.route || config?.defaultRoute || 'Appearance';
35
35
  const title = titleProp || config?.title;
@@ -41,7 +41,7 @@ export const AppearanceSection: React.FC<AppearanceSectionProps> = ({
41
41
  } else if (config?.onPress) {
42
42
  config.onPress();
43
43
  } else {
44
- navigation.navigate(route as never);
44
+ navigation.navigate(route as 'Appearance');
45
45
  }
46
46
  };
47
47
 
@@ -41,7 +41,9 @@ export const ColorPicker: React.FC<ColorPickerProps> = ({
41
41
  if (value === color) return;
42
42
  onValueChange(color);
43
43
  } catch (error) {
44
- // Log error for debugging while preventing UI crashes
44
+ if (__DEV__) {
45
+ console.error('[ColorPicker] Failed to change color:', error);
46
+ }
45
47
  // Optionally: Show user feedback about the error
46
48
  }
47
49
  }, [value, onValueChange]);
@@ -76,6 +76,7 @@ export const SupportSection: React.FC<SupportSectionProps> = ({
76
76
  } catch (error) {
77
77
  // If the passed onSubmit throws, we log it.
78
78
  if (__DEV__) {
79
+ console.error('[SupportSection] Failed to submit feedback:', error);
79
80
  }
80
81
  // Optionally keep modal open? Or close it?
81
82
  // If we keep it open, user can retry.
@@ -2,7 +2,7 @@
2
2
  * Feedback Form Hook
3
3
  */
4
4
 
5
- import { useState, useCallback } from 'react';
5
+ import { useState, useCallback, useRef, useEffect } from 'react';
6
6
  import type { FeedbackType, FeedbackRating } from '../../domain/entities/FeedbackEntity';
7
7
 
8
8
  export interface FeedbackFormState {
@@ -25,6 +25,14 @@ export function useFeedbackForm(defaultValues?: Partial<FeedbackFormState>) {
25
25
  ...defaultValues,
26
26
  });
27
27
 
28
+ // Use ref to avoid recreating reset callback when defaultValues changes
29
+ const defaultValuesRef = useRef(defaultValues);
30
+
31
+ // Keep ref in sync with latest defaultValues
32
+ useEffect(() => {
33
+ defaultValuesRef.current = defaultValues;
34
+ }, [defaultValues]);
35
+
28
36
  const setType = useCallback((type: FeedbackType) => {
29
37
  setFormState((prev) => ({ ...prev, type }));
30
38
  }, []);
@@ -42,8 +50,8 @@ export function useFeedbackForm(defaultValues?: Partial<FeedbackFormState>) {
42
50
  }, []);
43
51
 
44
52
  const reset = useCallback(() => {
45
- setFormState({ ...initialState, ...defaultValues });
46
- }, [defaultValues]);
53
+ setFormState({ ...initialState, ...defaultValuesRef.current });
54
+ }, []);
47
55
 
48
56
  const isValid = formState.title.trim().length > 0 && formState.description.trim().length > 0;
49
57
 
@@ -2,7 +2,7 @@
2
2
  * GamificationScreen AchievementsList Component
3
3
  */
4
4
 
5
- import React from "react";
5
+ import React, { useMemo } from "react";
6
6
  import { View, type TextStyle } from "react-native";
7
7
  import { AtomicText } from "@umituz/react-native-design-system";
8
8
  import { AchievementItem } from "../AchievementItem";
@@ -39,8 +39,20 @@ export const AchievementsList: React.FC<AchievementsListProps> = ({
39
39
  subtextColor,
40
40
  sectionTitleStyle,
41
41
  }) => {
42
- const unlockedAchievements = achievements.filter((a) => a.isUnlocked);
43
- const lockedAchievements = achievements.filter((a) => !a.isUnlocked);
42
+ // Optimize: Single pass through achievements array instead of two filter operations
43
+ const { unlocked, locked } = useMemo(() => {
44
+ return achievements.reduce<{ unlocked: typeof achievements; locked: typeof achievements }>(
45
+ (acc, achievement) => {
46
+ if (achievement.isUnlocked) {
47
+ acc.unlocked.push(achievement);
48
+ } else {
49
+ acc.locked.push(achievement);
50
+ }
51
+ return acc;
52
+ },
53
+ { unlocked: [], locked: [] }
54
+ );
55
+ }, [achievements]);
44
56
 
45
57
  return (
46
58
  <View style={styles.section}>
@@ -55,9 +67,9 @@ export const AchievementsList: React.FC<AchievementsListProps> = ({
55
67
  ) : (
56
68
  <>
57
69
  {/* Unlocked achievements first */}
58
- {unlockedAchievements.map((achievement, index) => (
70
+ {unlocked.map((achievement, index) => (
59
71
  <AchievementItem
60
- key={`unlocked-${index}`}
72
+ key={`unlocked-${achievement.title}-${index}`}
61
73
  {...achievement}
62
74
  accentColor={accentColor}
63
75
  backgroundColor={cardBackgroundColor}
@@ -67,9 +79,9 @@ export const AchievementsList: React.FC<AchievementsListProps> = ({
67
79
  ))}
68
80
 
69
81
  {/* Locked achievements */}
70
- {lockedAchievements.map((achievement, index) => (
82
+ {locked.map((achievement, index) => (
71
83
  <AchievementItem
72
- key={`locked-${index}`}
84
+ key={`locked-${achievement.title}-${index}`}
73
85
  {...achievement}
74
86
  accentColor={accentColor}
75
87
  backgroundColor={cardBackgroundColor}
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
2
  import { ViewStyle } from 'react-native';
3
- import { useAppNavigation } from '@umituz/react-native-design-system';
4
3
  import { LegalConfig } from '../../domain/entities/LegalConfig';
5
4
  import { SettingsItemCard } from '../../../../presentation/components/SettingsItemCard';
5
+ import { useSettingsNavigation } from '../../../../presentation/navigation/hooks/useSettingsNavigation';
6
6
 
7
7
  export interface LegalSectionProps {
8
8
  config?: LegalConfig;
@@ -27,7 +27,7 @@ export const LegalSection: React.FC<LegalSectionProps> = ({
27
27
  hideMargin,
28
28
  }) => {
29
29
 
30
- const navigation = useAppNavigation();
30
+ const navigation = useSettingsNavigation();
31
31
 
32
32
  const route = config?.route || config?.defaultRoute || 'Legal';
33
33
  const title = propsTitle || config?.title;
@@ -40,7 +40,7 @@ export const LegalSection: React.FC<LegalSectionProps> = ({
40
40
  } else if (config?.onPress) {
41
41
  config.onPress();
42
42
  } else {
43
- navigation.navigate(route as never);
43
+ navigation.navigate(route as 'Legal');
44
44
  }
45
45
  };
46
46
 
@@ -1,16 +1,16 @@
1
- import { useAppNavigation } from '@umituz/react-native-design-system';
2
1
  import { useLocalization } from '../hooks/useLocalization';
3
2
  import { languageRepository } from '../repository/LanguageRepository';
3
+ import { useSettingsNavigation } from '../../../../presentation/navigation/hooks/useSettingsNavigation';
4
4
 
5
5
 
6
6
  export const useLanguageNavigation = (navigationScreen: string) => {
7
- const navigation = useAppNavigation();
7
+ const navigation = useSettingsNavigation();
8
8
  const { currentLanguage } = useLocalization();
9
9
  const currentLang = languageRepository.getLanguageByCode(currentLanguage) || languageRepository.getDefaultLanguage();
10
10
 
11
11
  const navigateToLanguageSelection = () => {
12
12
  if (navigation && navigationScreen) {
13
- navigation.navigate(navigationScreen as never);
13
+ navigation.navigate(navigationScreen as 'LanguageSelection');
14
14
  }
15
15
  };
16
16
 
@@ -6,6 +6,7 @@
6
6
  import { useState, useMemo } from 'react';
7
7
  import { useLocalization } from './useLocalization';
8
8
  import { searchLanguages } from '../config/LanguageQuery';
9
+ import { devError } from '../../../../utils/devUtils';
9
10
 
10
11
  export const useLanguageSelection = () => {
11
12
  const { currentLanguage, setLanguage } = useLocalization();
@@ -17,16 +18,16 @@ export const useLanguageSelection = () => {
17
18
  }, [searchQuery]);
18
19
 
19
20
  const handleLanguageSelect = async (code: string, onComplete?: () => void) => {
20
- if (__DEV__) {
21
- }
22
- setSelectedCode(code);
23
- if (__DEV__) {
24
- }
25
- await setLanguage(code);
26
- if (__DEV__) {
27
- }
28
- onComplete?.();
29
- if (__DEV__) {
21
+ try {
22
+ setSelectedCode(code);
23
+ await setLanguage(code);
24
+ onComplete?.();
25
+ } catch (error) {
26
+ // Revert selection on error
27
+ setSelectedCode(currentLanguage);
28
+ devError('[useLanguageSelection] Failed to set language:', error);
29
+ // Re-throw for caller to handle
30
+ throw error;
30
31
  }
31
32
  };
32
33
 
@@ -7,6 +7,9 @@ export const useLocalization = () => {
7
7
  const store = useLocalizationStore();
8
8
  const { t } = useTranslationFunction();
9
9
 
10
+ // Destructure to avoid recreating callbacks when store object changes
11
+ const { isInitialized, initialize } = store;
12
+
10
13
  const getCurrentLanguageObject = useCallback((): Language | undefined => {
11
14
  return store.getCurrentLanguage();
12
15
  }, [store]);
@@ -16,8 +19,8 @@ export const useLocalization = () => {
16
19
  }, [store]);
17
20
 
18
21
  const handleInitialize = useCallback(async () => {
19
- await store.initialize();
20
- }, [store]);
22
+ await initialize();
23
+ }, [initialize]);
21
24
 
22
25
  const isLanguageSupported = useCallback((code: string) => {
23
26
  return store.isLanguageSupported(code);
@@ -28,10 +31,10 @@ export const useLocalization = () => {
28
31
  }, [store]);
29
32
 
30
33
  useEffect(() => {
31
- if (!store.isInitialized) {
32
- store.initialize();
34
+ if (!isInitialized) {
35
+ initialize();
33
36
  }
34
- }, [store.isInitialized, store]);
37
+ }, [isInitialized, initialize]);
35
38
 
36
39
  return {
37
40
  t,
@@ -4,10 +4,10 @@ import {
4
4
  useAppDesignTokens,
5
5
  AtomicText,
6
6
  ListItem,
7
- useAppNavigation,
8
7
  } from '@umituz/react-native-design-system';
9
8
  import { useLocalization } from '../../infrastructure/hooks/useLocalization';
10
9
  import { getLanguageByCode } from '../../infrastructure/config/languages';
10
+ import { useSettingsNavigation } from '../../../../presentation/navigation/hooks/useSettingsNavigation';
11
11
 
12
12
  export interface LanguageSectionConfig {
13
13
  route?: string;
@@ -30,7 +30,7 @@ export const LanguageSection: React.FC<LanguageSectionProps> = ({
30
30
  }) => {
31
31
  const tokens = useAppDesignTokens();
32
32
  const { currentLanguage } = useLocalization();
33
- const navigation = useAppNavigation();
33
+ const navigation = useSettingsNavigation();
34
34
 
35
35
  const route = config?.route || 'LanguageSelection';
36
36
  const title = config?.title || "";
@@ -46,7 +46,7 @@ export const LanguageSection: React.FC<LanguageSectionProps> = ({
46
46
  if (config?.onPress) {
47
47
  config.onPress();
48
48
  } else {
49
- navigation.navigate(route as never);
49
+ navigation.navigate(route as 'LanguageSelection');
50
50
  }
51
51
  };
52
52
 
@@ -32,6 +32,9 @@ export const LocalizationManager: React.FC<LocalizationProviderProps> = ({
32
32
  setIsI18nReady(true);
33
33
  }
34
34
  } catch (error) {
35
+ if (__DEV__) {
36
+ console.error('[LocalizationManager] Initialization failed:', error);
37
+ }
35
38
  if (isMounted) {
36
39
  setIsI18nReady(true); // Set ready even on error to prevent indefinite loading
37
40
  }
@@ -2,7 +2,7 @@
2
2
  * Simple notification settings hook
3
3
  */
4
4
 
5
- import { useEffect } from 'react';
5
+ import { useEffect, useRef } from 'react';
6
6
  import { createStore } from '@umituz/react-native-design-system';
7
7
 
8
8
  interface NotificationSettingsState {
@@ -34,11 +34,18 @@ export const useNotificationSettings = () => {
34
34
  const store = useNotificationSettingsStore();
35
35
  const { notificationsEnabled, isLoading, setNotificationsEnabled, initialize } = store;
36
36
 
37
- // Initialize on first mount
37
+ // Use ref to call initialize only once on mount
38
+ const initializeRef = useRef(initialize);
39
+
38
40
  useEffect(() => {
39
- initialize();
41
+ initializeRef.current = initialize;
40
42
  }, [initialize]);
41
43
 
44
+ // Initialize only once on mount
45
+ useEffect(() => {
46
+ initializeRef.current();
47
+ }, []);
48
+
42
49
  return {
43
50
  notificationsEnabled,
44
51
  setNotificationsEnabled,
@@ -1,7 +1,7 @@
1
1
  import React, { useCallback } from 'react';
2
2
  import type { StyleProp, ViewStyle } from 'react-native';
3
- import { useAppNavigation } from '@umituz/react-native-design-system';
4
3
  import { SettingsItemCard } from '../../../../presentation/components/SettingsItemCard';
4
+ import { useSettingsNavigation } from '../../../../presentation/navigation/hooks/useSettingsNavigation';
5
5
 
6
6
  export interface NotificationsSectionConfig {
7
7
  route?: string;
@@ -24,14 +24,14 @@ export const NotificationsSection: React.FC<NotificationsSectionProps> = ({
24
24
  noBackground,
25
25
  hideMargin,
26
26
  }) => {
27
- const navigation = useAppNavigation();
27
+ const navigation = useSettingsNavigation();
28
28
 
29
29
  const handlePress = useCallback(() => {
30
30
  if (config?.onPress) {
31
31
  config.onPress();
32
32
  } else {
33
33
  const route = config?.route || 'Notifications';
34
- navigation.navigate(route as never);
34
+ navigation.navigate(route as 'Notifications');
35
35
  }
36
36
  }, [config?.route, config?.onPress, navigation]);
37
37
 
@@ -3,7 +3,7 @@
3
3
  * Encapsulates DateTimePicker logic for notification settings
4
4
  */
5
5
 
6
- import { useState, useCallback } from 'react';
6
+ import { useState, useCallback, useRef, useEffect } from 'react';
7
7
  import type { QuietHoursConfig } from '../../infrastructure/services/types';
8
8
 
9
9
  export type PickerMode = 'start' | 'end' | null;
@@ -29,6 +29,14 @@ export const useTimePicker = ({
29
29
  }: UseTimePickerParams): TimePickerHandlers => {
30
30
  const [pickerMode, setPickerMode] = useState<PickerMode>(null);
31
31
 
32
+ // Use ref to avoid stale closure in handleTimeChange
33
+ const pickerModeRef = useRef<PickerMode>(null);
34
+
35
+ // Keep ref in sync with state
36
+ useEffect(() => {
37
+ pickerModeRef.current = pickerMode;
38
+ }, [pickerMode]);
39
+
32
40
  const handleStartTimePress = useCallback(() => {
33
41
  setPickerMode('start');
34
42
  }, []);
@@ -42,14 +50,16 @@ export const useTimePicker = ({
42
50
  const hours = selectedDate.getHours();
43
51
  const minutes = selectedDate.getMinutes();
44
52
 
45
- if (pickerMode === 'start') {
53
+ // Use ref to get current mode without recreating callback
54
+ if (pickerModeRef.current === 'start') {
46
55
  onStartTimeChange(hours, minutes);
47
- } else if (pickerMode === 'end') {
56
+ } else if (pickerModeRef.current === 'end') {
48
57
  onEndTimeChange(hours, minutes);
49
58
  }
50
59
  }
51
60
  setPickerMode(null);
52
- }, [pickerMode, onStartTimeChange, onEndTimeChange]);
61
+ pickerModeRef.current = null;
62
+ }, [onStartTimeChange, onEndTimeChange]);
53
63
 
54
64
  const getPickerDate = useCallback((): Date => {
55
65
  const date = new Date();
@@ -5,12 +5,12 @@
5
5
 
6
6
  import React, { useMemo, useCallback } from 'react';
7
7
  import { View, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
8
- import {
9
- AtomicText,
10
- AtomicIcon,
11
- AtomicSpinner,
12
- ScreenLayout,
13
- NavigationHeader,
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ AtomicSpinner,
12
+ ScreenLayout,
13
+ NavigationHeader,
14
14
  useAppNavigation,
15
15
  useAppDesignTokens
16
16
  } from '@umituz/react-native-design-system';
@@ -18,6 +18,7 @@ import { ReminderItem } from '../components/ReminderItem';
18
18
  import { useReminders, useRemindersLoading } from '../../infrastructure/storage/RemindersStore';
19
19
  import { useReminderActions } from '../../infrastructure/hooks/useReminderActions';
20
20
  import type { Reminder, ReminderTranslations } from '../../../infrastructure/services/types';
21
+ import { devError } from '../../../../../utils/devUtils';
21
22
 
22
23
  export interface ReminderListScreenProps {
23
24
  translations: ReminderTranslations;
@@ -41,11 +42,21 @@ export const ReminderListScreen: React.FC<ReminderListScreenProps> = ({
41
42
  const { toggleReminderEnabled, removeReminder } = useReminderActions();
42
43
 
43
44
  const handleToggle = useCallback(async (id: string) => {
44
- await toggleReminderEnabled(id);
45
+ try {
46
+ await toggleReminderEnabled(id);
47
+ } catch (error) {
48
+ devError('[ReminderListScreen] Failed to toggle reminder:', error);
49
+ // TODO: Show error toast to user
50
+ }
45
51
  }, [toggleReminderEnabled]);
46
52
 
47
53
  const handleDelete = useCallback(async (id: string) => {
48
- await removeReminder(id);
54
+ try {
55
+ await removeReminder(id);
56
+ } catch (error) {
57
+ devError('[ReminderListScreen] Failed to delete reminder:', error);
58
+ // TODO: Show error toast to user
59
+ }
49
60
  }, [removeReminder]);
50
61
 
51
62
  const canAddMore = reminders.length < maxReminders;
@@ -73,6 +73,7 @@ export function useAppRating(config: RatingConfig): UseAppRatingResult {
73
73
  }
74
74
  } catch (error) {
75
75
  if (typeof __DEV__ !== "undefined" && __DEV__) {
76
+ console.error('[useAppRating] Failed to request review:', error);
76
77
  }
77
78
  }
78
79
 
@@ -26,7 +26,7 @@ export function useMemoizedStyles<T>(
26
26
  * @param deps Dependencies for memoization
27
27
  * @returns Memoized callback
28
28
  */
29
- export function useMemoizedCallback<T extends (...args: any[]) => any>(
29
+ export function useMemoizedCallback<T extends (...args: never[]) => unknown>(
30
30
  callback: T,
31
31
  deps: DependencyList
32
32
  ): T {
@@ -52,7 +52,7 @@ export function useMemoizedValue<T>(
52
52
  * @param deps Dependencies for memoization
53
53
  * @returns Memoized styles
54
54
  */
55
- export function useStyledMemo<T extends Record<string, any>>(
55
+ export function useStyledMemo<T extends Record<string, unknown>>(
56
56
  styleCreator: () => T,
57
57
  deps: DependencyList = []
58
58
  ): T {
@@ -119,7 +119,7 @@ export function useMemoWithEquality<T>(
119
119
  * @param delay Delay in milliseconds
120
120
  * @returns Debounced callback
121
121
  */
122
- export function useDebouncedCallback<T extends (...args: any[]) => any>(
122
+ export function useDebouncedCallback<T extends (...args: never[]) => unknown>(
123
123
  callback: T,
124
124
  delay: number
125
125
  ): T {
@@ -150,7 +150,7 @@ export function useDebouncedCallback<T extends (...args: any[]) => any>(
150
150
  * @param delay Delay in milliseconds
151
151
  * @returns Throttled callback
152
152
  */
153
- export function useThrottledCallback<T extends (...args: any[]) => any>(
153
+ export function useThrottledCallback<T extends (...args: never[]) => unknown>(
154
154
  callback: T,
155
155
  delay: number
156
156
  ): T {
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Settings Error Boundary
3
+ *
4
+ * Generic error boundary component for Settings domains.
5
+ * Catches JavaScript errors in child component tree and displays fallback UI.
6
+ */
7
+
8
+ import React, { Component, ReactNode } from 'react';
9
+ import { View, Text, StyleSheet } from 'react-native';
10
+ import { devError } from '../../../utils/devUtils';
11
+
12
+ export interface SettingsErrorBoundaryProps {
13
+ children: ReactNode;
14
+ domainName: string;
15
+ fallback?: ReactNode;
16
+ onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
17
+ }
18
+
19
+ export interface SettingsErrorBoundaryState {
20
+ hasError: boolean;
21
+ error: Error | null;
22
+ }
23
+
24
+ /**
25
+ * Error Boundary for Settings domains
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * <SettingsErrorBoundary domainName="Appearance">
30
+ * <AppearanceSection {...props} />
31
+ * </SettingsErrorBoundary>
32
+ * ```
33
+ */
34
+ export class SettingsErrorBoundary extends Component<
35
+ SettingsErrorBoundaryProps,
36
+ SettingsErrorBoundaryState
37
+ > {
38
+ constructor(props: SettingsErrorBoundaryProps) {
39
+ super(props);
40
+ this.state = { hasError: false, error: null };
41
+ }
42
+
43
+ static getDerivedStateFromError(error: Error): SettingsErrorBoundaryState {
44
+ return { hasError: true, error };
45
+ }
46
+
47
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
48
+ const { domainName, onError } = this.props;
49
+
50
+ // Log error in development
51
+ devError(`[${domainName}] Error caught by boundary:`, error, errorInfo);
52
+
53
+ // Call custom error handler if provided
54
+ onError?.(error, errorInfo);
55
+ }
56
+
57
+ private handleReset = (): void => {
58
+ this.setState({ hasError: false, error: null });
59
+ };
60
+
61
+ render(): ReactNode {
62
+ const { hasError } = this.state;
63
+ const { children, fallback, domainName } = this.props;
64
+
65
+ if (hasError) {
66
+ // Use custom fallback if provided
67
+ if (fallback) {
68
+ return fallback;
69
+ }
70
+
71
+ // Default fallback UI
72
+ return (
73
+ <View style={styles.container}>
74
+ <Text style={styles.title}>Something went wrong</Text>
75
+ <Text style={styles.message}>
76
+ {domainName} encountered an error. Please try again.
77
+ </Text>
78
+ </View>
79
+ );
80
+ }
81
+
82
+ return children;
83
+ }
84
+ }
85
+
86
+ const styles = StyleSheet.create({
87
+ container: {
88
+ flex: 1,
89
+ justifyContent: 'center',
90
+ alignItems: 'center',
91
+ padding: 16,
92
+ },
93
+ title: {
94
+ fontSize: 18,
95
+ fontWeight: '600',
96
+ marginBottom: 8,
97
+ textAlign: 'center',
98
+ },
99
+ message: {
100
+ fontSize: 14,
101
+ color: '#666',
102
+ textAlign: 'center',
103
+ marginBottom: 16,
104
+ },
105
+ });
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Error Boundary exports
3
+ */
4
+
5
+ export { SettingsErrorBoundary } from './SettingsErrorBoundary';
6
+ export type {
7
+ SettingsErrorBoundaryProps,
8
+ SettingsErrorBoundaryState,
9
+ } from './SettingsErrorBoundary';
@@ -162,12 +162,12 @@ export const useSettingsScreenConfig = (
162
162
  onSignIn: handleSignIn,
163
163
  onLogout: handleSignOut,
164
164
  onDeleteAccount: handleDeleteAccount,
165
- translations: translations?.account,
165
+ translations: translations?.account as any,
166
166
  }), [user, userProfileData, handleSignIn, handleSignOut, handleDeleteAccount, translations]);
167
167
 
168
168
  // Use centralized FAQ translation
169
169
  const translatedFaqData = useMemo(() =>
170
- translateFAQData(faqData, (key: string) => "", appInfo),
170
+ translateFAQData(faqData, (_key: string) => "", appInfo),
171
171
  [faqData, appInfo]
172
172
  );
173
173
 
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Typed Settings Navigation Hook
3
+ *
4
+ * Provides type-safe navigation for Settings stack screens.
5
+ * Replaces unsafe `as never` casts throughout the codebase.
6
+ */
7
+
8
+ import { useNavigation } from '@react-navigation/native';
9
+ import type { StackNavigationProp } from '@react-navigation/stack';
10
+ import type { SettingsStackParamList } from '../types';
11
+
12
+ /**
13
+ * Type for Settings navigation prop
14
+ */
15
+ export type SettingsNavigationProp = StackNavigationProp<SettingsStackParamList>;
16
+
17
+ /**
18
+ * Hook to get typed navigation for Settings screens
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const navigation = useSettingsNavigation();
23
+ * navigation.navigate('LanguageSelection'); // Fully typed!
24
+ * ```
25
+ */
26
+ export const useSettingsNavigation = () => {
27
+ return useNavigation<SettingsNavigationProp>();
28
+ };
@@ -17,12 +17,18 @@ import {
17
17
  combineScreens,
18
18
  } from "../../utils/screenFactory";
19
19
  import type { SettingsStackNavigatorProps } from "../types";
20
+ import type { AboutConfig } from "../../../domains/about/domain/entities/AppInfo";
21
+ import type { LegalScreenProps } from "../../../domains/legal/presentation/screens/LegalScreen";
22
+ import type {
23
+ NotificationSettingsTranslations,
24
+ QuietHoursTranslations
25
+ } from "../../../domains/notifications/infrastructure/services/types";
20
26
 
21
27
  export interface UseSettingsScreensProps extends SettingsStackNavigatorProps {
22
- aboutConfig: any;
23
- legalProps: any;
24
- notificationTranslations: any;
25
- quietHoursTranslations: any;
28
+ aboutConfig: AboutConfig;
29
+ legalProps: LegalScreenProps;
30
+ notificationTranslations: NotificationSettingsTranslations;
31
+ quietHoursTranslations: QuietHoursTranslations;
26
32
  }
27
33
 
28
34
  export const useSettingsScreens = (props: UseSettingsScreensProps): StackScreen[] => {
@@ -1,11 +1,11 @@
1
1
  import React from "react";
2
- import { useAppNavigation } from "@umituz/react-native-design-system";
3
2
  import type { IconName } from "@umituz/react-native-design-system";
4
3
  import { SettingsItemCard } from "../../components/SettingsItemCard";
5
4
  import { useGamification } from "../../../domains/gamification";
6
5
  import type { GamificationItemConfig } from "../types/UserFeatureConfig";
7
6
  import type { GamificationSettingsConfig } from "../../../domains/gamification";
8
7
  import { compareGamificationProps } from "../../../infrastructure/utils/memoComparisonUtils";
8
+ import { useSettingsNavigation } from "../../navigation/hooks/useSettingsNavigation";
9
9
 
10
10
  export interface GamificationSettingsItemProps {
11
11
  config: GamificationItemConfig;
@@ -21,12 +21,12 @@ const GamificationSettingsItemComponent: React.FC<GamificationSettingsItemProps>
21
21
  noBackground,
22
22
  hideMargin,
23
23
  }) => {
24
- const navigation = useAppNavigation();
24
+ const navigation = useSettingsNavigation();
25
25
  const { level } = useGamification(gamificationConfig);
26
26
 
27
27
  const handlePress = React.useCallback(() => {
28
28
  const route = config.route || "Gamification";
29
- navigation.navigate(route as never);
29
+ navigation.navigate(route as 'Gamification');
30
30
  }, [navigation, config.route]);
31
31
 
32
32
  const icon = (config.icon || "trophy-outline") as IconName;
@@ -16,7 +16,6 @@ import type { SettingsContentProps } from "./types/SettingsContentProps";
16
16
  import { SubscriptionSettingsItem } from "./SubscriptionSettingsItem";
17
17
  import { WalletSettingsItem } from "./WalletSettingsItem";
18
18
  import { GamificationSettingsItem } from "./GamificationSettingsItem";
19
- import { VideoTutorialSettingsItem } from "./VideoTutorialSettingsItem";
20
19
 
21
20
  export const SettingsContent: React.FC<SettingsContentProps> = ({
22
21
  normalizedConfig,
@@ -1,20 +1,20 @@
1
1
  import React from "react";
2
- import { useAppNavigation } from "@umituz/react-native-design-system";
3
2
  import { SettingsItemCard } from "../../components/SettingsItemCard";
4
3
  import type { IconName } from "@umituz/react-native-design-system";
5
4
  import type { SubscriptionConfig } from "../types/UserFeatureConfig";
6
5
  import { compareConfigAndTranslate } from "../../../infrastructure/utils/memoComparisonUtils";
6
+ import { useSettingsNavigation } from "../../navigation/hooks/useSettingsNavigation";
7
7
 
8
8
  export interface SubscriptionSettingsItemProps {
9
9
  config: SubscriptionConfig;
10
10
  }
11
11
 
12
12
  const SubscriptionSettingsItemComponent: React.FC<SubscriptionSettingsItemProps> = ({ config }) => {
13
- const navigation = useAppNavigation();
13
+ const navigation = useSettingsNavigation();
14
14
 
15
15
  const handlePress = React.useCallback(() => {
16
16
  if (config.route) {
17
- navigation.navigate(config.route as never);
17
+ navigation.navigate(config.route as any);
18
18
  } else if (config.onPress) {
19
19
  config.onPress();
20
20
  }
@@ -1,9 +1,9 @@
1
1
  import React from "react";
2
- import { useAppNavigation } from "@umituz/react-native-design-system";
3
2
  import type { IconName } from "@umituz/react-native-design-system";
4
3
  import { SettingsItemCard } from "../../components/SettingsItemCard";
5
4
  import type { VideoTutorialConfig } from "../types/UserFeatureConfig";
6
5
  import { compareConfigAndTranslate } from "../../../infrastructure/utils/memoComparisonUtils";
6
+ import { useSettingsNavigation } from "../../navigation/hooks/useSettingsNavigation";
7
7
 
8
8
  export interface VideoTutorialSettingsItemProps {
9
9
  config: VideoTutorialConfig;
@@ -16,14 +16,14 @@ const VideoTutorialSettingsItemComponent: React.FC<VideoTutorialSettingsItemProp
16
16
  noBackground,
17
17
  hideMargin,
18
18
  }) => {
19
- const navigation = useAppNavigation();
19
+ const navigation = useSettingsNavigation();
20
20
 
21
21
  const handlePress = React.useCallback(() => {
22
22
  if (config.onPress) {
23
23
  config.onPress();
24
24
  } else {
25
25
  const route = config.route || "VideoTutorial";
26
- navigation.navigate(route as never);
26
+ navigation.navigate(route as 'VideoTutorial');
27
27
  }
28
28
  }, [navigation, config.onPress, config.route]);
29
29
 
@@ -1,20 +1,20 @@
1
1
  import React from "react";
2
- import { useAppNavigation } from "@umituz/react-native-design-system";
3
2
  import { SettingsItemCard } from "../../components/SettingsItemCard";
4
3
  import type { IconName } from "@umituz/react-native-design-system";
5
4
  import type { WalletConfig } from "../types/UserFeatureConfig";
6
5
  import { compareConfigAndTranslate } from "../../../infrastructure/utils/memoComparisonUtils";
6
+ import { useSettingsNavigation } from "../../navigation/hooks/useSettingsNavigation";
7
7
 
8
8
  export interface WalletSettingsItemProps {
9
9
  config: WalletConfig;
10
10
  }
11
11
 
12
12
  const WalletSettingsItemComponent: React.FC<WalletSettingsItemProps> = ({ config }) => {
13
- const navigation = useAppNavigation();
13
+ const navigation = useSettingsNavigation();
14
14
 
15
15
  const handlePress = React.useCallback(() => {
16
16
  if (config.route) {
17
- navigation.navigate(config.route as never);
17
+ navigation.navigate(config.route as any);
18
18
  }
19
19
  }, [navigation, config.route]);
20
20
 
@@ -1,5 +1,4 @@
1
1
  import React from "react";
2
- import { useAppNavigation } from "@umituz/react-native-design-system";
3
2
  import { AppearanceSection } from "../../../../domains/appearance/presentation/components/AppearanceSection";
4
3
  import { NotificationsSection } from "../../../../domains/notifications";
5
4
  import { getLanguageByCode } from "../../../../domains/localization";
@@ -7,6 +6,7 @@ import { SettingsItemCard } from "../../../components/SettingsItemCard";
7
6
  import type { NormalizedConfig } from "../../utils/normalizeConfig";
8
7
  import { SettingsSection } from "../../../components/SettingsSection";
9
8
  import { compareConfigAndFeatures } from "../../../../infrastructure/utils/memoComparisonUtils";
9
+ import { useSettingsNavigation } from "../../../navigation/hooks/useSettingsNavigation";
10
10
 
11
11
  interface FeatureSettingsSectionProps {
12
12
  normalizedConfig: NormalizedConfig;
@@ -24,14 +24,14 @@ export const FeatureSettingsSection: React.FC<FeatureSettingsSectionProps> = ({
24
24
  currentLanguage,
25
25
  }) => {
26
26
  const translations = normalizedConfig.translations;
27
- const navigation = useAppNavigation();
27
+ const navigation = useSettingsNavigation();
28
28
 
29
29
  const handleLanguagePress = React.useCallback(() => {
30
30
  if (normalizedConfig.language.config?.onPress) {
31
31
  normalizedConfig.language.config.onPress();
32
32
  } else {
33
33
  const route = normalizedConfig.language.config?.route || "LanguageSelection";
34
- navigation.navigate(route as never);
34
+ navigation.navigate(route as 'LanguageSelection');
35
35
  }
36
36
  }, [navigation, normalizedConfig.language.config]);
37
37
 
@@ -1,11 +1,11 @@
1
1
  import React, { useCallback } from "react";
2
- import { useAppNavigation } from "@umituz/react-native-design-system";
3
2
  import { SupportSection } from "../../../../domains/feedback/presentation/components/SupportSection";
4
3
  import { SettingsSection } from "../../../components/SettingsSection";
5
4
  import { SettingsItemCard } from "../../../components/SettingsItemCard";
6
5
  import { VideoTutorialSettingsItem } from "../VideoTutorialSettingsItem";
7
6
  import type { NormalizedConfig } from "../../utils/normalizeConfig";
8
7
  import { compareConfigAndFeatures } from "../../../../infrastructure/utils/memoComparisonUtils";
8
+ import { useSettingsNavigation } from "../../../navigation/hooks/useSettingsNavigation";
9
9
 
10
10
  interface SupportSettingsSectionProps {
11
11
  features: { feedback: boolean; rating: boolean; faqs: boolean; [key: string]: boolean };
@@ -17,10 +17,10 @@ export const SupportSettingsSection: React.FC<SupportSettingsSectionProps> = ({
17
17
  normalizedConfig,
18
18
  }) => {
19
19
  const translations = normalizedConfig.translations;
20
- const navigation = useAppNavigation();
20
+ const navigation = useSettingsNavigation();
21
21
 
22
22
  const handleFAQPress = useCallback(() => {
23
- navigation.navigate("FAQ" as never);
23
+ navigation.navigate("FAQ");
24
24
  }, [navigation]);
25
25
 
26
26
  if (!(features.feedback || features.rating || features.faqs || features.videoTutorial)) return null;
@@ -9,7 +9,6 @@ import type {
9
9
  AchievementDefinition,
10
10
  LevelDefinition,
11
11
  } from "../../../domains/gamification/types";
12
- import type { TranslationFunction } from "./types";
13
12
 
14
13
  /**
15
14
  * Create subscription configuration
@@ -2,10 +2,15 @@
2
2
  * Config Creator Types
3
3
  */
4
4
 
5
+ /**
6
+ * Translation options for i18n parameters
7
+ */
8
+ export type TranslationOptions = Record<string, string | number | boolean>;
9
+
5
10
  /**
6
11
  * Translation function type
7
12
  */
8
- export type TranslationFunction = (key: string, options?: any) => string;
13
+ export type TranslationFunction = (key: string, options?: TranslationOptions) => string;
9
14
 
10
15
  /**
11
16
  * Feedback form data interface
@@ -6,6 +6,7 @@
6
6
 
7
7
  import type { FAQData } from "../navigation/types";
8
8
  import type { AppInfo } from "../navigation/types";
9
+ import type { TranslationOptions } from "./config-creators/types";
9
10
 
10
11
  /**
11
12
  * Utility for handling FAQ data
@@ -13,7 +14,7 @@ import type { AppInfo } from "../navigation/types";
13
14
  */
14
15
  export const translateFAQData = (
15
16
  faqData: FAQData | undefined,
16
- _t: (key: string, params?: any) => string,
17
+ _t: (key: string, params?: TranslationOptions) => string,
17
18
  _appInfo: AppInfo
18
19
  ): FAQData | undefined => {
19
20
  return faqData;
@@ -11,9 +11,9 @@ import type { AdditionalScreen } from "../navigation/types";
11
11
  /**
12
12
  * Create a basic stack screen configuration
13
13
  */
14
- export function createStackScreen(
14
+ export function createStackScreen<P = unknown>(
15
15
  name: string,
16
- componentOrChildren: React.ComponentType<any> | (() => React.ReactElement),
16
+ componentOrChildren: React.ComponentType<P> | (() => React.ReactElement),
17
17
  options: { headerShown?: boolean } = {}
18
18
  ): StackScreen {
19
19
  const isChildrenFunction = typeof componentOrChildren === "function" &&
@@ -28,7 +28,7 @@ export function createStackScreen(
28
28
  }
29
29
  return {
30
30
  name,
31
- component: componentOrChildren as any,
31
+ component: componentOrChildren as React.ComponentType<P>,
32
32
  options: { headerShown: false, ...options },
33
33
  };
34
34
  }
@@ -53,7 +53,7 @@ export function createScreenWithProps<P>(
53
53
  * Convert additional screen to stack screen
54
54
  */
55
55
  export function convertAdditionalScreen(screen: AdditionalScreen): StackScreen {
56
- const stackScreen: any = { name: screen.name };
56
+ const stackScreen: Partial<StackScreen> = { name: screen.name };
57
57
  if (screen.component) stackScreen.component = screen.component;
58
58
  if (screen.children) stackScreen.children = screen.children;
59
59
  if (screen.options) stackScreen.options = screen.options;
@@ -22,7 +22,31 @@ declare const __DEV__: boolean;
22
22
  */
23
23
  export const useAuthHandlers = (appInfo: AppInfo, translations?: SettingsTranslations["errors"]) => {
24
24
  const { signOut } = useAuth();
25
- const { deleteAccount } = useAccountManagement();
25
+
26
+ const passwordPrompt = useCallback(async (): Promise<string | null> => {
27
+ return new Promise((resolve) => {
28
+ Alert.prompt(
29
+ "Password Required",
30
+ "Please enter your password to delete your account",
31
+ [
32
+ {
33
+ text: "Cancel",
34
+ style: "cancel",
35
+ onPress: () => resolve(null),
36
+ },
37
+ {
38
+ text: "Confirm",
39
+ onPress: (password) => resolve(password || null),
40
+ },
41
+ ],
42
+ "secure-text"
43
+ );
44
+ });
45
+ }, []);
46
+
47
+ const { deleteAccount } = useAccountManagement({
48
+ onPasswordRequired: passwordPrompt,
49
+ });
26
50
  const { showAuthModal } = useAuthModalStore();
27
51
 
28
52
  const handleRatePress = useCallback(async () => {
@@ -68,12 +92,25 @@ export const useAuthHandlers = (appInfo: AppInfo, translations?: SettingsTransla
68
92
  try {
69
93
  await deleteAccount();
70
94
  } catch (error) {
95
+ const errorMessage = error instanceof Error ? error.message : String(error);
96
+
71
97
  if (typeof __DEV__ !== "undefined" && __DEV__) {
72
98
  console.error("[useAuthHandlers] Delete account failed:", error);
73
99
  }
100
+
101
+ // More specific error messages
102
+ if (errorMessage.includes("Password required") || errorMessage.includes("password")) {
103
+ Alert.alert(
104
+ "Password Required",
105
+ "Please enter your password when prompted to confirm account deletion.",
106
+ [{ text: "OK" }]
107
+ );
108
+ return;
109
+ }
110
+
74
111
  AlertService.createErrorAlert(
75
- translations?.common || "",
76
- translations?.deleteAccountError || ""
112
+ translations?.common || "Error",
113
+ errorMessage || translations?.deleteAccountError || "Failed to delete account"
77
114
  );
78
115
  }
79
116
  }, [deleteAccount, translations]);
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Development Mode Utilities
3
+ *
4
+ * Provides safe, standardized helpers for development-only code.
5
+ * All utilities check for __DEV__ existence to prevent runtime errors.
6
+ */
7
+
8
+ /**
9
+ * Safe development mode check
10
+ * Handles cases where __DEV__ is not defined
11
+ *
12
+ * @returns true if in development mode, false otherwise
13
+ */
14
+ export const isDev = (): boolean => {
15
+ return typeof __DEV__ !== 'undefined' && __DEV__;
16
+ };
17
+
18
+ /**
19
+ * Log only in development mode
20
+ *
21
+ * @param args - Arguments to log
22
+ */
23
+ export const devLog = (...args: unknown[]): void => {
24
+ if (isDev()) {
25
+ console.log('[DEV]', ...args);
26
+ }
27
+ };
28
+
29
+ /**
30
+ * Warn only in development mode
31
+ *
32
+ * @param args - Arguments to warn
33
+ */
34
+ export const devWarn = (...args: unknown[]): void => {
35
+ if (isDev()) {
36
+ console.warn('[DEV]', ...args);
37
+ }
38
+ };
39
+
40
+ /**
41
+ * Error only in development mode
42
+ *
43
+ * @param args - Arguments to error
44
+ */
45
+ export const devError = (...args: unknown[]): void => {
46
+ if (isDev()) {
47
+ console.error('[DEV]', ...args);
48
+ }
49
+ };
50
+
51
+ /**
52
+ * Execute callback only in development mode
53
+ *
54
+ * @param callback - Callback to execute in dev mode
55
+ */
56
+ export const devOnly = (callback: () => void): void => {
57
+ if (isDev()) {
58
+ callback();
59
+ }
60
+ };