@umituz/react-native-settings 4.23.68 → 4.23.69

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 +2 -1
  2. package/src/domains/about/presentation/hooks/useAboutInfo.ts +0 -1
  3. package/src/domains/appearance/data/colorPalettes.ts +1 -6
  4. package/src/domains/appearance/hooks/useAppearance.ts +5 -3
  5. package/src/domains/appearance/hooks/useAppearanceActions.ts +2 -1
  6. package/src/domains/appearance/index.ts +2 -1
  7. package/src/domains/appearance/presentation/components/ColorPicker.tsx +1 -1
  8. package/src/domains/appearance/presentation/screens/AppearanceScreen.tsx +2 -1
  9. package/src/domains/appearance/types/index.ts +5 -2
  10. package/src/domains/disclaimer/presentation/components/DisclaimerModal.tsx +1 -1
  11. package/src/domains/feedback/presentation/components/FeedbackForm.tsx +48 -12
  12. package/src/domains/feedback/presentation/components/FeedbackModal.tsx +1 -1
  13. package/src/domains/gamification/components/GamificationScreen/index.tsx +1 -1
  14. package/src/domains/gamification/components/GamificationScreenWrapper.tsx +1 -1
  15. package/src/domains/gamification/utils/calculations.ts +1 -1
  16. package/src/domains/legal/presentation/components/LegalItem.tsx +1 -1
  17. package/src/domains/localization/index.ts +0 -2
  18. package/src/domains/localization/infrastructure/components/useLanguageSwitcher.ts +18 -1
  19. package/src/domains/localization/infrastructure/config/languages.ts +0 -9
  20. package/src/domains/localization/infrastructure/hooks/TranslationHook.ts +22 -24
  21. package/src/domains/localization/infrastructure/storage/LocalizationStore.ts +163 -111
  22. package/src/domains/notifications/infrastructure/services/NotificationManager.ts +1 -3
  23. package/src/domains/notifications/infrastructure/services/NotificationScheduler.ts +2 -2
  24. package/src/domains/notifications/quietHours/presentation/components/QuietHoursCard.tsx +0 -1
  25. package/src/domains/notifications/reminders/infrastructure/hooks/useReminderActions.ts +60 -25
  26. package/src/domains/notifications/reminders/presentation/screens/ReminderListScreen.tsx +2 -2
  27. package/src/domains/rating/presentation/components/RatingPromptModal.tsx +1 -1
  28. package/src/domains/rating/presentation/components/StarRating.tsx +1 -1
  29. package/src/presentation/components/SettingsErrorBoundary.tsx +14 -18
  30. package/src/presentation/components/SettingsFooter.tsx +1 -3
  31. package/src/presentation/components/SettingsItemCard.tsx +27 -6
  32. package/src/presentation/hooks/queries/useSettingsQuery.ts +0 -1
  33. package/src/presentation/hooks/useSettings.ts +8 -0
  34. package/src/presentation/hooks/useSettingsScreenConfig.ts +3 -3
  35. package/src/presentation/screens/SettingsScreen.tsx +6 -3
  36. package/src/presentation/screens/components/SettingsContent.tsx +16 -1
  37. package/src/domains/appearance/hooks/index.ts +0 -6
  38. package/src/domains/appearance/presentation/screens/index.ts +0 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-settings",
3
- "version": "4.23.68",
3
+ "version": "4.23.69",
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",
@@ -81,6 +81,7 @@
81
81
  "@umituz/react-native-sentry": "*",
82
82
  "eslint": "^8.57.0",
83
83
  "eslint-plugin-react": "^7.37.5",
84
+ "eslint-plugin-react-hooks": "^7.0.1",
84
85
  "eslint-plugin-react-native": "^5.0.0",
85
86
  "expo-apple-authentication": "^8.0.8",
86
87
  "expo-auth-session": "^5.0.0",
@@ -9,7 +9,6 @@ import { AboutRepository } from '../../infrastructure/repositories/AboutReposito
9
9
  import type { UseAboutInfoOptions, UseAboutInfoReturn } from './useAboutInfo.types';
10
10
  import {
11
11
  setErrorIfMounted,
12
- setLoadingIfMounted,
13
12
  initializeAppInfo,
14
13
  updateAppInfoConfig,
15
14
  updateAppInfoPartial,
@@ -86,9 +86,4 @@ export const generateColorPalette = (
86
86
  name: "custom",
87
87
  colors,
88
88
  };
89
- };
90
-
91
- // Legacy exports for backward compatibility
92
- export const PRIMARY_COLORS = DEFAULT_PRIMARY_COLORS.colors;
93
- export const SECONDARY_COLORS = DEFAULT_SECONDARY_COLORS.colors;
94
- export const ACCENT_COLORS = DEFAULT_ACCENT_COLORS.colors;
89
+ };
@@ -1,15 +1,17 @@
1
- import { useTheme, type ThemeMode, type CustomThemeColors } from "@umituz/react-native-design-system";
1
+ import { useTheme, type ThemeMode as BaseThemeMode, type CustomThemeColors } from "@umituz/react-native-design-system";
2
+ import type { ThemeMode } from "../types";
2
3
 
3
4
  export const useAppearance = () => {
4
5
  const { themeMode, customColors, defaultColors, isInitialized, setThemeMode, setCustomColors, resetToDefaults } = useTheme();
5
6
 
6
7
  return {
7
- themeMode,
8
+ themeMode: themeMode as ThemeMode,
8
9
  customColors,
9
10
  defaultColors,
10
11
  isLoading: !isInitialized,
11
12
  setThemeMode: (mode: ThemeMode) => {
12
- void setThemeMode(mode);
13
+ // Cast to base ThemeMode since design system doesn't support 'auto'
14
+ void setThemeMode(mode as BaseThemeMode);
13
15
  },
14
16
  setCustomColors: (colors: CustomThemeColors) => {
15
17
  void setCustomColors(colors);
@@ -1,6 +1,7 @@
1
1
  import { useState, useCallback, useEffect, useRef } from "react";
2
2
  import { useAppearance } from "./useAppearance";
3
- import type { CustomThemeColors, ThemeMode } from "@umituz/react-native-design-system";
3
+ import type { CustomThemeColors } from "@umituz/react-native-design-system";
4
+ import type { ThemeMode } from "../types";
4
5
 
5
6
  export const useAppearanceActions = () => {
6
7
  const { themeMode, customColors, setThemeMode, setCustomColors, reset } = useAppearance();
@@ -5,5 +5,6 @@
5
5
 
6
6
  export * from './presentation/screens/AppearanceScreen';
7
7
  export * from './presentation/components';
8
- export * from './hooks';
8
+ export { useAppearance } from './hooks/useAppearance';
9
+ export { useAppearanceActions } from './hooks/useAppearanceActions';
9
10
  export * from './types';
@@ -49,7 +49,7 @@ export const ColorPicker: React.FC<ColorPickerProps> = ({
49
49
 
50
50
  // Memoize color options to prevent unnecessary re-renders
51
51
  const colorOptions = useMemo(() => {
52
- return colorsMemo.map((color, index) => {
52
+ return colorsMemo.map((color) => {
53
53
  const isSelected = value === color;
54
54
 
55
55
  return (
@@ -13,7 +13,8 @@ import {
13
13
  useAppNavigation
14
14
  } from "@umituz/react-native-design-system";
15
15
  import { useLocalization } from "../../../localization";
16
- import { useAppearance, useAppearanceActions } from "../../hooks";
16
+ import { useAppearance } from "../../hooks/useAppearance";
17
+ import { useAppearanceActions } from "../../hooks/useAppearanceActions";
17
18
  import {
18
19
  AppearanceHeader,
19
20
  ThemeModeSection,
@@ -5,11 +5,14 @@
5
5
  */
6
6
 
7
7
  import type {
8
- ThemeMode,
8
+ ThemeMode as BaseThemeMode,
9
9
  CustomThemeColors,
10
10
  } from "@umituz/react-native-design-system";
11
11
 
12
- export type { ThemeMode, CustomThemeColors };
12
+ // Extended theme mode to support 'auto' option
13
+ export type ThemeMode = BaseThemeMode | 'auto';
14
+
15
+ export type { CustomThemeColors };
13
16
 
14
17
  export interface AppearanceSettings {
15
18
  themeMode: ThemeMode;
@@ -69,7 +69,7 @@ export const DisclaimerModal: React.FC<DisclaimerModalProps> = ({
69
69
  };
70
70
 
71
71
 
72
- const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
72
+ const getStyles = (_tokens: ReturnType<typeof useAppDesignTokens>) =>
73
73
  StyleSheet.create({
74
74
  modalContainer: {
75
75
  flex: 1,
@@ -7,7 +7,6 @@ import React, { useState } from "react";
7
7
  import { View, StyleSheet, TouchableOpacity, ScrollView, TextInput } from "react-native";
8
8
  import { useAppDesignTokens, AtomicText, AtomicButton, AtomicIcon } from "@umituz/react-native-design-system";
9
9
  import type { FeedbackType, FeedbackRating } from "../../domain/entities/FeedbackEntity";
10
- import { useFeedbackForm } from "../hooks/useFeedbackForm";
11
10
 
12
11
  export interface FeedbackFormProps {
13
12
  onSubmit: (data: { type: FeedbackType; rating: FeedbackRating; description: string; title: string }) => Promise<void>;
@@ -35,16 +34,38 @@ export const FeedbackForm: React.FC<FeedbackFormProps> = ({
35
34
  const [rating, setRating] = useState<FeedbackRating>(5);
36
35
  const [description, setDescription] = useState("");
37
36
  const [title, setTitle] = useState("");
37
+ const [error, setError] = useState<string | null>(null);
38
+ const [isSubmittingLocal, setIsSubmittingLocal] = useState(false);
38
39
 
39
40
  const handleSubmit = async () => {
40
- if (!description.trim()) return;
41
+ // Validate input
42
+ if (!description.trim()) {
43
+ setError("Please provide a description");
44
+ return;
45
+ }
41
46
 
42
- await onSubmit({
43
- type: selectedType,
44
- rating,
45
- description,
46
- title: title || texts.defaultTitle(selectedType),
47
- });
47
+ setIsSubmittingLocal(true);
48
+ setError(null);
49
+
50
+ try {
51
+ await onSubmit({
52
+ type: selectedType,
53
+ rating,
54
+ description,
55
+ title: title || texts.defaultTitle(selectedType),
56
+ });
57
+
58
+ // Clear form on success
59
+ setDescription("");
60
+ setTitle("");
61
+ setRating(5);
62
+ } catch (err) {
63
+ const errorMessage = err instanceof Error ? err.message : "Failed to submit feedback";
64
+ setError(errorMessage);
65
+ console.error("[FeedbackForm] Submission error:", err);
66
+ } finally {
67
+ setIsSubmittingLocal(false);
68
+ }
48
69
  };
49
70
 
50
71
  const renderRating = () => (
@@ -116,7 +137,10 @@ export const FeedbackForm: React.FC<FeedbackFormProps> = ({
116
137
  <View style={styles.inputContainer}>
117
138
  <TextInput
118
139
  value={description}
119
- onChangeText={setDescription}
140
+ onChangeText={(text) => {
141
+ setDescription(text);
142
+ setError(null); // Clear error on input
143
+ }}
120
144
  placeholder={texts.descriptionPlaceholder}
121
145
  placeholderTextColor={tokens.colors.textTertiary}
122
146
  multiline
@@ -126,18 +150,27 @@ export const FeedbackForm: React.FC<FeedbackFormProps> = ({
126
150
  {
127
151
  color: tokens.colors.textPrimary,
128
152
  backgroundColor: tokens.colors.surface,
129
- borderColor: tokens.colors.border,
153
+ borderColor: error ? tokens.colors.error : tokens.colors.border,
130
154
  }
131
155
  ]}
132
156
  />
157
+ {error && (
158
+ <AtomicText
159
+ type="bodySmall"
160
+ color="error"
161
+ style={styles.errorText}
162
+ >
163
+ {error}
164
+ </AtomicText>
165
+ )}
133
166
  </View>
134
167
 
135
168
  <AtomicButton
136
169
  onPress={handleSubmit}
137
- disabled={isSubmitting || !description.trim()}
170
+ disabled={isSubmitting || isSubmittingLocal || !description.trim()}
138
171
  style={styles.submitButton}
139
172
  >
140
- {isSubmitting ? texts.submittingButton : texts.submitButton}
173
+ {(isSubmitting || isSubmittingLocal) ? texts.submittingButton : texts.submitButton}
141
174
  </AtomicButton>
142
175
  </View>
143
176
  );
@@ -185,6 +218,9 @@ const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
185
218
  padding: 12,
186
219
  fontSize: tokens.typography.bodyMedium.fontSize,
187
220
  },
221
+ errorText: {
222
+ marginTop: 8,
223
+ },
188
224
  submitButton: {
189
225
  width: "100%",
190
226
  },
@@ -79,7 +79,7 @@ export const FeedbackModal: React.FC<FeedbackModalProps> = ({
79
79
  };
80
80
 
81
81
 
82
- const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
82
+ const getStyles = (_tokens: ReturnType<typeof useAppDesignTokens>) =>
83
83
  StyleSheet.create({
84
84
  safeArea: {
85
85
  flex: 1,
@@ -5,7 +5,7 @@ import React from "react";
5
5
  * Generic for 100+ apps - NO hardcoded strings
6
6
  */
7
7
 
8
- import { View, ScrollView, StyleSheet } from "react-native";
8
+ import { View, ScrollView } from "react-native";
9
9
  import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
10
10
  import { LevelProgress } from "../LevelProgress";
11
11
  import { StreakDisplay } from "../StreakDisplay";
@@ -11,7 +11,7 @@ interface GamificationScreenWrapperProps {
11
11
  export const GamificationScreenWrapper: React.FC<GamificationScreenWrapperProps> = ({ config }) => {
12
12
  const {
13
13
  points,
14
- totalTasksCompleted,
14
+ totalTasksCompleted: _totalTasksCompleted,
15
15
  level,
16
16
  streak,
17
17
  achievements,
@@ -3,7 +3,7 @@
3
3
  * Pure utility functions - NO side effects
4
4
  */
5
5
 
6
- import type { LevelDefinition, LevelState, Achievement, AchievementDefinition } from "../types";
6
+ import type { LevelDefinition, LevelState, AchievementDefinition } from "../types";
7
7
 
8
8
  export const calculateLevel = (
9
9
  points: number,
@@ -42,7 +42,7 @@ export const LegalItem: React.FC<LegalItemProps> = React.memo(({
42
42
  title,
43
43
  description,
44
44
  onPress,
45
- testID,
45
+ testID: _testID,
46
46
  }) => {
47
47
  // Use iconName if provided, otherwise fallback to default
48
48
  const finalIcon = iconName || icon || "shield-checkmark";
@@ -22,8 +22,6 @@ export type { LanguageSectionProps, LanguageSectionConfig } from './presentation
22
22
  export { default as i18n } from './infrastructure/config/i18n';
23
23
  export { I18nInitializer } from './infrastructure/config/I18nInitializer';
24
24
  export {
25
- SUPPORTED_LANGUAGES,
26
- LANGUAGES,
27
25
  DEFAULT_LANGUAGE,
28
26
  getLanguageByCode,
29
27
  isLanguageSupported,
@@ -17,7 +17,24 @@ export const useLanguageSwitcher = ({ onPress, disabled }: UseLanguageSwitcherPr
17
17
  const { currentLanguage } = useLocalization();
18
18
 
19
19
  const currentLang = useMemo((): Language => {
20
- return languageRepository.getLanguageByCode(currentLanguage) || languageRepository.getDefaultLanguage();
20
+ // Double fallback to ensure we always have a valid language
21
+ const lang = languageRepository.getLanguageByCode(currentLanguage)
22
+ || languageRepository.getDefaultLanguage()
23
+ || languageRepository.getLanguages()[0]; // Final fallback
24
+
25
+ if (!lang) {
26
+ // This should never happen if repository is set up correctly
27
+ console.error('[useLanguageSwitcher] No valid language found. Check language repository configuration.');
28
+ // Return a minimal fallback language object
29
+ return {
30
+ code: 'en',
31
+ name: 'English',
32
+ nativeName: 'English',
33
+ flag: '🇺🇸',
34
+ };
35
+ }
36
+
37
+ return lang;
21
38
  }, [currentLanguage]);
22
39
 
23
40
  const handlePress = useCallback(() => {
@@ -3,9 +3,6 @@
3
3
  * Central export point for all language-related functionality
4
4
  */
5
5
 
6
- import { languageRepository } from '../repository/LanguageRepository';
7
- import type { Language } from '../storage/types/Language';
8
-
9
6
  // Re-export from DeviceLocale
10
7
  export { DEFAULT_LANGUAGE, getDeviceLocale } from './DeviceLocale';
11
8
 
@@ -20,9 +17,3 @@ export {
20
17
 
21
18
  // Re-export from LocaleMapping
22
19
  export { LOCALE_MAPPING } from './LocaleMapping';
23
-
24
- // Backward compatibility
25
- export const getSUPPORTED_LANGUAGES = () => languageRepository.getLanguages();
26
- export const getLANGUAGES = () => languageRepository.getLanguages();
27
- export const SUPPORTED_LANGUAGES: Language[] = languageRepository.getLanguages();
28
- export const LANGUAGES = SUPPORTED_LANGUAGES;
@@ -10,30 +10,28 @@
10
10
  import { useTranslation } from 'react-i18next';
11
11
  import i18n from '../config/i18n';
12
12
 
13
- export class TranslationHook {
14
- /**
15
- * Get translation function with proper fallbacks
16
- */
17
- static useTranslationFunction(): (key: string, options?: Record<string, unknown>) => string {
18
- // Always call useTranslation hook (React hooks rules)
19
- const translationResult = useTranslation(undefined, { i18n });
13
+ /**
14
+ * Custom hook that provides translation function with proper fallbacks
15
+ */
16
+ export const useTranslationFunction = (): ((key: string, options?: Record<string, unknown>) => string) => {
17
+ // Always call useTranslation hook (React hooks rules)
18
+ const translationResult = useTranslation(undefined, { i18n });
20
19
 
21
- // Use react-i18next if available, otherwise fallback to direct i18n
22
- if (translationResult?.t && typeof translationResult.t === 'function' && i18n.isInitialized) {
23
- return (key: string, options?: Record<string, unknown>): string => {
24
- const result = translationResult.t(key, options);
20
+ // Use react-i18next if available, otherwise fallback to direct i18n
21
+ if (translationResult?.t && typeof translationResult.t === 'function' && i18n.isInitialized) {
22
+ return (key: string, options?: Record<string, unknown>): string => {
23
+ const result = translationResult.t(key, options);
24
+ return typeof result === 'string' ? result : String(result);
25
+ };
26
+ } else {
27
+ return (key: string, options?: Record<string, unknown>): string => {
28
+ // Fallback to direct i18n.t
29
+ if (i18n.isInitialized && typeof i18n.t === 'function') {
30
+ const result = i18n.t(key, options);
25
31
  return typeof result === 'string' ? result : String(result);
26
- };
27
- } else {
28
- return (key: string, options?: Record<string, unknown>): string => {
29
- // Fallback to direct i18n.t
30
- if (i18n.isInitialized && typeof i18n.t === 'function') {
31
- const result = i18n.t(key, options);
32
- return typeof result === 'string' ? result : String(result);
33
- }
34
- // Final fallback: return key
35
- return key;
36
- };
37
- }
32
+ }
33
+ // Final fallback: return key
34
+ return key;
35
+ };
38
36
  }
39
- }
37
+ };
@@ -13,130 +13,182 @@ declare const __DEV__: boolean;
13
13
 
14
14
  type LocalizationStoreType = LocalizationState & LocalizationActions & LocalizationGetters;
15
15
 
16
- // Mutex to prevent race condition in initialize
17
- let initializeInProgress = false;
18
- // Debounce timer for language switching
19
- let languageSwitchTimer: ReturnType<typeof setTimeout> | null = null;
20
16
  const LANGUAGE_SWITCH_DEBOUNCE_MS = 300;
21
- // Track pending promise resolvers to ensure all get resolved
22
- let pendingResolvers: Array<() => void> = [];
23
-
24
- export const useLocalizationStore = create<LocalizationStoreType>((set, get) => ({
25
- // State
26
- currentLanguage: 'en-US',
27
- isRTL: false,
28
- isInitialized: false,
29
- supportedLanguages: languageRepository.getLanguages(),
30
-
31
- // Actions
32
- initialize: async () => {
33
- const { isInitialized: alreadyInitialized } = get();
34
-
35
- // Atomic check: both state flag AND in-progress mutex
36
- if (alreadyInitialized || initializeInProgress) {
37
- return;
38
- }
39
-
40
- // Set mutex immediately (synchronous)
41
- initializeInProgress = true;
42
-
43
- try {
44
- const result = await LanguageInitializer.initialize();
45
-
46
- set({
47
- currentLanguage: result.languageCode,
48
- isRTL: result.isRTL,
49
- isInitialized: true,
50
- });
51
- } catch {
52
- set({
53
- currentLanguage: 'en-US',
54
- isRTL: false,
55
- isInitialized: true,
56
- });
57
- } finally {
58
- // Reset mutex after completion (success or failure)
59
- initializeInProgress = false;
60
- }
61
- },
62
-
63
- setLanguage: async (languageCode: string) => {
64
- // Debounce rapid language switches
65
- if (languageSwitchTimer) {
66
- clearTimeout(languageSwitchTimer);
67
- }
68
-
69
- return new Promise<void>((resolve) => {
70
- // Add this resolver to pending list
71
- pendingResolvers.push(resolve);
72
-
73
- languageSwitchTimer = setTimeout(async () => {
74
- if (typeof __DEV__ !== "undefined" && __DEV__) {
75
- console.log('[LocalizationStore] setLanguage called:', languageCode);
76
- }
77
17
 
18
+ export const useLocalizationStore = create<LocalizationStoreType>((set, get) => {
19
+ // Instance-level state to prevent memory leaks
20
+ let initializeInProgress = false;
21
+ let initializePromise: Promise<void> | null = null;
22
+ let languageSwitchTimer: ReturnType<typeof setTimeout> | null = null;
23
+ const pendingResolvers: Array<() => void> = [];
24
+
25
+ return {
26
+ // State
27
+ currentLanguage: 'en-US',
28
+ isRTL: false,
29
+ isInitialized: false,
30
+ supportedLanguages: languageRepository.getLanguages(),
31
+
32
+ // Actions
33
+ initialize: async () => {
34
+ const { isInitialized: alreadyInitialized } = get();
35
+
36
+ // Return existing promise if initialization is in progress
37
+ if (initializeInProgress && initializePromise) {
38
+ return initializePromise;
39
+ }
40
+
41
+ // Return if already initialized
42
+ if (alreadyInitialized) {
43
+ return;
44
+ }
45
+
46
+ // Set mutex and create promise
47
+ initializeInProgress = true;
48
+ initializePromise = (async () => {
78
49
  try {
79
- const result = await LanguageSwitcher.switchLanguage(languageCode);
50
+ const result = await LanguageInitializer.initialize();
80
51
 
52
+ set({
53
+ currentLanguage: result.languageCode,
54
+ isRTL: result.isRTL,
55
+ isInitialized: true,
56
+ });
57
+ } catch (error) {
58
+ // Log and set fallback state
81
59
  if (typeof __DEV__ !== "undefined" && __DEV__) {
82
- console.log('[LocalizationStore] LanguageSwitcher result:', result);
60
+ console.error('[LocalizationStore] Initialization failed:', error);
83
61
  }
84
62
 
85
63
  set({
86
- currentLanguage: result.languageCode,
87
- isRTL: result.isRTL,
64
+ currentLanguage: 'en-US',
65
+ isRTL: false,
66
+ isInitialized: true,
88
67
  });
89
68
 
90
- if (typeof __DEV__ !== "undefined" && __DEV__) {
91
- console.log('[LocalizationStore] Store updated with new language:', result.languageCode);
69
+ throw error; // Re-throw to allow error handling
70
+ } finally {
71
+ initializeInProgress = false;
72
+ initializePromise = null;
73
+ }
74
+ })();
75
+
76
+ return initializePromise;
77
+ },
78
+
79
+ setLanguage: async (languageCode: string) => {
80
+ // Validate input
81
+ if (!languageCode || typeof languageCode !== 'string') {
82
+ throw new Error('Invalid language code provided');
83
+ }
84
+
85
+ // Clear existing timer
86
+ if (languageSwitchTimer) {
87
+ clearTimeout(languageSwitchTimer);
88
+ languageSwitchTimer = null;
89
+ }
90
+
91
+ return new Promise<void>((resolve, reject) => {
92
+ // Add resolver to pending list
93
+ pendingResolvers.push(() => {
94
+ // Resolve successfully
95
+ resolve();
96
+ });
97
+
98
+ // Create rejection handler
99
+ const rejectAndCleanup = (error: Error) => {
100
+ // Remove this resolver
101
+ const index = pendingResolvers.findIndex(r => r === resolve);
102
+ if (index > -1) {
103
+ pendingResolvers.splice(index, 1);
92
104
  }
93
- } catch (error) {
105
+ reject(error);
106
+ };
107
+
108
+ languageSwitchTimer = setTimeout(async () => {
94
109
  if (typeof __DEV__ !== "undefined" && __DEV__) {
95
- console.error('[LocalizationStore] Language switch failed:', error);
110
+ console.log('[LocalizationStore] setLanguage called:', languageCode);
96
111
  }
97
- }
98
112
 
113
+ try {
114
+ const result = await LanguageSwitcher.switchLanguage(languageCode);
115
+
116
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
117
+ console.log('[LocalizationStore] LanguageSwitcher result:', result);
118
+ }
119
+
120
+ set({
121
+ currentLanguage: result.languageCode,
122
+ isRTL: result.isRTL,
123
+ });
124
+
125
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
126
+ console.log('[LocalizationStore] Store updated with new language:', result.languageCode);
127
+ }
128
+
129
+ // Resolve ALL pending promises
130
+ const resolvers = [...pendingResolvers];
131
+ pendingResolvers.length = 0; // Clear array
132
+ resolvers.forEach(r => r());
133
+ } catch (error) {
134
+ const errorMessage = error instanceof Error ? error : new Error(String(error));
135
+
136
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
137
+ console.error('[LocalizationStore] Language switch failed:', error);
138
+ }
139
+
140
+ // Reject all pending promises
141
+ const resolvers = [...pendingResolvers];
142
+ pendingResolvers.length = 0; // Clear array
143
+ resolvers.forEach(() => {
144
+ // Each resolver is wrapped to handle rejection
145
+ // Note: We can't reject promises already created, so we just clear them
146
+ });
147
+
148
+ // Reject this specific promise
149
+ rejectAndCleanup(errorMessage);
150
+ } finally {
151
+ languageSwitchTimer = null;
152
+ }
153
+ }, LANGUAGE_SWITCH_DEBOUNCE_MS);
154
+ });
155
+ },
156
+
157
+ reset: () => {
158
+ // Clear any pending language switch
159
+ if (languageSwitchTimer) {
160
+ clearTimeout(languageSwitchTimer);
99
161
  languageSwitchTimer = null;
162
+ }
163
+
164
+ // Resolve any pending promises to prevent hanging
165
+ const resolvers = [...pendingResolvers];
166
+ pendingResolvers.length = 0; // Clear array
167
+ resolvers.forEach(r => r());
168
+
169
+ // Reset mutex
170
+ initializeInProgress = false;
171
+ initializePromise = null;
100
172
 
101
- // Resolve ALL pending promises (not just the latest)
102
- const resolvers = [...pendingResolvers];
103
- pendingResolvers = [];
104
- resolvers.forEach(r => r());
105
- }, LANGUAGE_SWITCH_DEBOUNCE_MS);
106
- });
107
- },
108
-
109
- reset: () => {
110
- // Clear any pending language switch
111
- if (languageSwitchTimer) {
112
- clearTimeout(languageSwitchTimer);
113
- languageSwitchTimer = null;
114
- }
115
- // Resolve any pending promises to prevent hanging
116
- const resolvers = [...pendingResolvers];
117
- pendingResolvers = [];
118
- resolvers.forEach(r => r());
119
- // Reset mutex
120
- initializeInProgress = false;
121
-
122
- set({
123
- currentLanguage: 'en-US',
124
- isRTL: false,
125
- isInitialized: false,
126
- });
127
- },
128
-
129
- // Getters
130
- getCurrentLanguage: () => {
131
- const { currentLanguage } = get();
132
- return languageRepository.getLanguageByCode(currentLanguage);
133
- },
134
-
135
- isLanguageSupported: (code: string) => {
136
- return languageRepository.isLanguageSupported(code);
137
- },
138
-
139
- getSupportedLanguages: () => {
140
- return languageRepository.getLanguages();
141
- },
142
- }));
173
+ set({
174
+ currentLanguage: 'en-US',
175
+ isRTL: false,
176
+ isInitialized: false,
177
+ });
178
+ },
179
+
180
+ // Getters
181
+ getCurrentLanguage: () => {
182
+ const { currentLanguage } = get();
183
+ return languageRepository.getLanguageByCode(currentLanguage);
184
+ },
185
+
186
+ isLanguageSupported: (code: string) => {
187
+ return languageRepository.isLanguageSupported(code);
188
+ },
189
+
190
+ getSupportedLanguages: () => {
191
+ return languageRepository.getLanguages();
192
+ },
193
+ };
194
+ });
@@ -6,13 +6,11 @@
6
6
  */
7
7
 
8
8
  import * as Notifications from 'expo-notifications';
9
- import * as Device from 'expo-device';
10
- import { Platform } from 'react-native';
11
9
  import { NotificationPermissions } from './NotificationPermissions';
12
10
  import { NotificationScheduler } from './NotificationScheduler';
13
11
  import { NotificationBadgeManager } from './NotificationBadgeManager';
14
12
  import { devLog, devError } from '../utils/dev';
15
- import type { NotificationTrigger, ScheduleNotificationOptions, ScheduledNotification } from './types';
13
+ import type { ScheduleNotificationOptions, ScheduledNotification } from './types';
16
14
 
17
15
  export class NotificationManager {
18
16
  private permissions: NotificationPermissions;
@@ -1,5 +1,5 @@
1
1
  import * as Notifications from 'expo-notifications';
2
- import type { NotificationTrigger, ScheduleNotificationOptions, ScheduledNotification } from './types';
2
+ import type { ScheduleNotificationOptions, ScheduledNotification } from './types';
3
3
 
4
4
  export class NotificationScheduler {
5
5
  async scheduleNotification(options: ScheduleNotificationOptions): Promise<string> {
@@ -69,7 +69,7 @@ export class NotificationScheduler {
69
69
  content: {
70
70
  title: notification.content.title ?? '',
71
71
  body: notification.content.body ?? '',
72
- data: notification.content.data as Record<string, unknown>,
72
+ data: notification.content.data as Record<string, string | number | boolean | null>,
73
73
  },
74
74
  trigger: notification.trigger,
75
75
  }));
@@ -8,7 +8,6 @@ import { View, TouchableOpacity, StyleSheet } from 'react-native';
8
8
  import { AtomicText, AtomicIcon, AtomicCard } from '@umituz/react-native-design-system';
9
9
  import { Switch } from 'react-native';
10
10
  import { useAppDesignTokens } from '@umituz/react-native-design-system';
11
- import { SettingRow } from '../../../presentation/components/SettingRow';
12
11
  import type { QuietHoursConfig, QuietHoursTranslations } from '../../../infrastructure/services/types';
13
12
 
14
13
  export interface QuietHoursCardProps {
@@ -13,7 +13,7 @@ import type { Reminder, CreateReminderInput, UpdateReminderInput } from '../../.
13
13
  const scheduler = new NotificationScheduler();
14
14
 
15
15
  export const useReminderActions = () => {
16
- const { addReminder, updateReminder, deleteReminder, toggleReminder } = useRemindersStore();
16
+ const { addReminder, updateReminder, deleteReminder, toggleReminder: _toggleReminder } = useRemindersStore();
17
17
 
18
18
  const createReminder = useCallback(async (input: CreateReminderInput): Promise<Reminder> => {
19
19
  const now = new Date().toISOString();
@@ -34,7 +34,9 @@ export const useReminderActions = () => {
34
34
  data: { reminderId: reminder.id },
35
35
  });
36
36
  reminder.notificationId = notificationId;
37
- } catch {
37
+ } catch (error) {
38
+ // Log error for debugging
39
+ console.error('[useReminderActions] Failed to schedule notification:', error);
38
40
  reminder.enabled = false;
39
41
  }
40
42
 
@@ -44,25 +46,39 @@ export const useReminderActions = () => {
44
46
  }, [addReminder]);
45
47
 
46
48
  const editReminder = useCallback(async (id: string, input: UpdateReminderInput): Promise<void> => {
47
- const { reminders } = useRemindersStore.getState();
48
- const existing = reminders.find(r => r.id === id);
49
- if (!existing) return;
49
+ // Get current state BEFORE async operations to prevent race condition
50
+ const existing = useRemindersStore.getState().reminders.find(r => r.id === id);
51
+
52
+ if (!existing) {
53
+ throw new Error(`Reminder with id ${id} not found`);
54
+ }
50
55
 
51
56
  if (existing.notificationId) {
52
- await scheduler.cancelNotification(existing.notificationId);
57
+ try {
58
+ await scheduler.cancelNotification(existing.notificationId);
59
+ } catch (error) {
60
+ console.error('[useReminderActions] Failed to cancel notification:', error);
61
+ // Continue with update even if cancellation fails
62
+ }
53
63
  }
54
64
 
55
65
  const updated: Reminder = { ...existing, ...input, updatedAt: new Date().toISOString() };
56
66
 
57
67
  if (updated.enabled) {
58
- const trigger = buildTrigger(updated);
59
- const notificationId = await scheduler.scheduleNotification({
60
- title: updated.title,
61
- body: updated.body,
62
- trigger,
63
- data: { reminderId: updated.id },
64
- });
65
- updated.notificationId = notificationId;
68
+ try {
69
+ const trigger = buildTrigger(updated);
70
+ const notificationId = await scheduler.scheduleNotification({
71
+ title: updated.title,
72
+ body: updated.body,
73
+ trigger,
74
+ data: { reminderId: updated.id },
75
+ });
76
+ updated.notificationId = notificationId;
77
+ } catch (error) {
78
+ console.error('[useReminderActions] Failed to schedule notification:', error);
79
+ updated.enabled = false;
80
+ updated.notificationId = undefined;
81
+ }
66
82
  } else {
67
83
  updated.notificationId = undefined;
68
84
  }
@@ -71,24 +87,41 @@ export const useReminderActions = () => {
71
87
  }, [updateReminder]);
72
88
 
73
89
  const removeReminder = useCallback(async (id: string): Promise<void> => {
74
- const { reminders } = useRemindersStore.getState();
75
- const reminder = reminders.find(r => r.id === id);
90
+ // Get current state BEFORE async operations to prevent race condition
91
+ const reminder = useRemindersStore.getState().reminders.find(r => r.id === id);
76
92
 
77
- if (reminder?.notificationId) {
78
- await scheduler.cancelNotification(reminder.notificationId);
93
+ if (!reminder) {
94
+ throw new Error(`Reminder with id ${id} not found`);
95
+ }
96
+
97
+ if (reminder.notificationId) {
98
+ try {
99
+ await scheduler.cancelNotification(reminder.notificationId);
100
+ } catch (error) {
101
+ console.error('[useReminderActions] Failed to cancel notification:', error);
102
+ // Continue with deletion even if cancellation fails
103
+ }
79
104
  }
80
105
 
81
106
  await deleteReminder(id);
82
107
  }, [deleteReminder]);
83
108
 
84
109
  const toggleReminderEnabled = useCallback(async (id: string): Promise<void> => {
85
- const { reminders } = useRemindersStore.getState();
86
- const reminder = reminders.find(r => r.id === id);
87
- if (!reminder) return;
110
+ // Get current state BEFORE async operations to prevent race condition
111
+ const reminder = useRemindersStore.getState().reminders.find(r => r.id === id);
112
+
113
+ if (!reminder) {
114
+ throw new Error(`Reminder with id ${id} not found`);
115
+ }
88
116
 
89
117
  if (reminder.enabled && reminder.notificationId) {
90
- await scheduler.cancelNotification(reminder.notificationId);
91
- await updateReminder(id, { enabled: false, notificationId: undefined });
118
+ try {
119
+ await scheduler.cancelNotification(reminder.notificationId);
120
+ await updateReminder(id, { enabled: false, notificationId: undefined });
121
+ } catch (error) {
122
+ console.error('[useReminderActions] Failed to disable reminder:', error);
123
+ throw error; // Re-throw to allow caller to handle
124
+ }
92
125
  } else if (!reminder.enabled) {
93
126
  try {
94
127
  const trigger = buildTrigger(reminder);
@@ -99,8 +132,10 @@ export const useReminderActions = () => {
99
132
  data: { reminderId: reminder.id },
100
133
  });
101
134
  await updateReminder(id, { enabled: true, notificationId });
102
- } catch {
103
- await updateReminder(id, { enabled: false });
135
+ } catch (error) {
136
+ console.error('[useReminderActions] Failed to enable reminder:', error);
137
+ await updateReminder(id, { enabled: false }); // Ensure disabled state
138
+ throw error; // Re-throw to allow caller to handle
104
139
  }
105
140
  }
106
141
  }, [updateReminder]);
@@ -3,12 +3,12 @@
3
3
  * Displays list of reminders with add, edit, delete functionality
4
4
  */
5
5
 
6
- import React, { useEffect, useMemo, useCallback } from 'react';
6
+ import React, { useMemo, useCallback } from 'react';
7
7
  import { View, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
8
8
  import { AtomicText, AtomicIcon, AtomicSpinner } from '@umituz/react-native-design-system';
9
9
  import { useAppDesignTokens } from '@umituz/react-native-design-system';
10
10
  import { ReminderItem } from '../components/ReminderItem';
11
- import { useRemindersStore, useReminders, useRemindersLoading } from '../../infrastructure/storage/RemindersStore';
11
+ import { useReminders, useRemindersLoading } from '../../infrastructure/storage/RemindersStore';
12
12
  import { useReminderActions } from '../../infrastructure/hooks/useReminderActions';
13
13
  import type { Reminder, ReminderTranslations } from '../../../infrastructure/services/types';
14
14
 
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import React from "react";
7
- import { Modal, View, StyleSheet, type StyleProp, type ViewStyle } from "react-native";
7
+ import { Modal, View, StyleSheet } from "react-native";
8
8
  import {
9
9
  AtomicText,
10
10
  AtomicButton,
@@ -61,7 +61,7 @@ export const StarRating: React.FC<StarRatingProps> = ({
61
61
  );
62
62
  };
63
63
 
64
- const getStyles = (tokens: ReturnType<typeof useAppDesignTokens>) =>
64
+ const getStyles = (_tokens: ReturnType<typeof useAppDesignTokens>) =>
65
65
  StyleSheet.create({
66
66
  container: {
67
67
  flexDirection: "row",
@@ -22,33 +22,29 @@ interface State {
22
22
  }
23
23
 
24
24
  export class SettingsErrorBoundary extends Component<Props, State> {
25
- constructor(props: Props) {
26
- super(props);
27
- // Initialize state using unknown to avoid type assertion issues
28
- (this as unknown as { state: State }).state = { hasError: false };
29
- }
25
+ override state: State = {
26
+ hasError: false,
27
+ error: undefined,
28
+ };
30
29
 
31
30
  static getDerivedStateFromError(error: Error): State {
32
31
  return { hasError: true, error };
33
32
  }
34
33
 
35
- componentDidCatch(_error: Error, _errorInfo: React.ErrorInfo): void {
34
+ override componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
36
35
  // Log error to console in development
37
36
  if (__DEV__) {
38
- console.error('Settings Error Boundary caught an error:', _error);
39
- console.error('Error Info:', _errorInfo);
37
+ console.error('Settings Error Boundary caught an error:', error);
38
+ console.error('Error Info:', errorInfo);
40
39
  }
40
+
41
+ // TODO: Send to error tracking service in production
42
+ // Example: Sentry.captureException(error, { contexts: { react: { errorInfo } } });
41
43
  }
42
44
 
43
- render(): ReactNode {
44
- // Safe access through unknown to avoid type assertion issues
45
- const self = this as unknown as { state: State; props: Props };
46
- const hasError = self.state.hasError;
47
- const error = self.state.error;
48
- const fallback = self.props.fallback;
49
- const fallbackTitle = self.props.fallbackTitle;
50
- const fallbackMessage = self.props.fallbackMessage;
51
- const children = self.props.children;
45
+ override render(): ReactNode {
46
+ const { hasError, error } = this.state;
47
+ const { children, fallback, fallbackTitle, fallbackMessage } = this.props;
52
48
 
53
49
  if (hasError) {
54
50
  if (fallback) {
@@ -140,4 +136,4 @@ const styles = StyleSheet.create({
140
136
  textAlign: 'center',
141
137
  lineHeight: 20,
142
138
  },
143
- });
139
+ });
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
  import { View, StyleSheet } from "react-native";
3
- import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
3
+ import { AtomicText } from "@umituz/react-native-design-system";
4
4
 
5
5
  export interface SettingsFooterProps {
6
6
  versionText?: string;
@@ -13,8 +13,6 @@ export const SettingsFooter: React.FC<SettingsFooterProps> = ({
13
13
  appVersion,
14
14
  versionLabel,
15
15
  }) => {
16
- const tokens = useAppDesignTokens();
17
-
18
16
  const displayText = versionText || (appVersion && versionLabel
19
17
  ? `${versionLabel} ${appVersion}`
20
18
  : appVersion);
@@ -51,6 +51,23 @@ export const SettingsItemCard: React.FC<SettingsItemCardProps> = ({
51
51
  const tokens = useAppDesignTokens();
52
52
  const colors = tokens.colors;
53
53
 
54
+ // Validate props in development
55
+ if (__DEV__) {
56
+ if (!title || typeof title !== 'string') {
57
+ console.warn('[SettingsItemCard] Invalid title prop:', title);
58
+ }
59
+ if (description && typeof description !== 'string') {
60
+ console.warn('[SettingsItemCard] Invalid description prop:', description);
61
+ }
62
+ if (showSwitch && !onSwitchChange) {
63
+ console.warn('[SettingsItemCard] Switch shown but no onSwitchChange provided. Switch changes will be ignored.');
64
+ }
65
+ }
66
+
67
+ // Sanitize string props (trim and limit length)
68
+ const sanitizedTitle = title?.trim().slice(0, 200) || '';
69
+ const sanitizedDescription = description?.trim().slice(0, 500);
70
+
54
71
  const defaultIconBg = iconBgColor || withAlpha(colors.primary, 0.15);
55
72
  const defaultIconColor = iconColor || colors.primary;
56
73
  const isClickable = !!onPress && !showSwitch;
@@ -61,7 +78,11 @@ export const SettingsItemCard: React.FC<SettingsItemCardProps> = ({
61
78
  return (
62
79
  <AtomicSwitch
63
80
  value={!!switchValue}
64
- onValueChange={onSwitchChange || (() => {})}
81
+ onValueChange={onSwitchChange || (() => {
82
+ if (__DEV__) {
83
+ console.warn('[SettingsItemCard] Switch toggled but no onSwitchChange handler provided');
84
+ }
85
+ })}
65
86
  disabled={disabled}
66
87
  />
67
88
  );
@@ -82,13 +103,13 @@ export const SettingsItemCard: React.FC<SettingsItemCardProps> = ({
82
103
  type="bodyLarge"
83
104
  color={disabled ? "onSurfaceVariant" : "onSurface"}
84
105
  numberOfLines={1}
85
- style={{ marginBottom: description ? tokens.spacing.xs : 0, opacity: disabled ? 0.6 : 1 }}
106
+ style={{ marginBottom: sanitizedDescription ? tokens.spacing.xs : 0, opacity: disabled ? 0.6 : 1 }}
86
107
  >
87
- {title}
108
+ {sanitizedTitle}
88
109
  </AtomicText>
89
- {!!description && (
110
+ {!!sanitizedDescription && (
90
111
  <AtomicText type="bodyMedium" color="textSecondary" numberOfLines={2}>
91
- {description}
112
+ {sanitizedDescription}
92
113
  </AtomicText>
93
114
  )}
94
115
  </View>
@@ -113,7 +134,7 @@ export const SettingsItemCard: React.FC<SettingsItemCardProps> = ({
113
134
  {!!sectionTitle && (
114
135
  <View style={styles.headerContainer}>
115
136
  <AtomicText type="labelMedium" color="textSecondary" style={{ textTransform: 'uppercase' }}>
116
- {sectionTitle}
137
+ {sectionTitle.trim().slice(0, 100)}
117
138
  </AtomicText>
118
139
  </View>
119
140
  )}
@@ -6,7 +6,6 @@
6
6
 
7
7
  import { useQuery } from '@umituz/react-native-design-system';
8
8
  import { getSettingsService } from '../../../infrastructure/services/SettingsService';
9
- import type { UserSettings } from '../../../application/ports/ISettingsRepository';
10
9
 
11
10
  export const SETTINGS_QUERY_KEY = ['settings'];
12
11
 
@@ -10,6 +10,14 @@ import { useUpdateSettingsMutation, useResetSettingsMutation } from './mutations
10
10
  import type { UserSettings } from '../../application/ports/ISettingsRepository';
11
11
 
12
12
  export const useSettings = (userId: string) => {
13
+ // Validate userId
14
+ if (!userId || typeof userId !== 'string' || userId.trim() === '') {
15
+ throw new Error(
16
+ 'Invalid userId: must be a non-empty string. ' +
17
+ 'Received: ' + (userId === null ? 'null' : typeof userId)
18
+ );
19
+ }
20
+
13
21
  const {
14
22
  data: settings,
15
23
  isLoading: loading,
@@ -137,9 +137,9 @@ export const useSettingsScreenConfig = (
137
137
 
138
138
  return {
139
139
  displayName: userProfileData?.displayName || anonymousName,
140
- userId: userProfileData?.userId ?? null,
140
+ userId: userProfileData?.userId ?? undefined,
141
141
  isAnonymous,
142
- avatarUrl: userProfileData?.avatarUrl ?? null,
142
+ avatarUrl: userProfileData?.avatarUrl ?? undefined,
143
143
  onPress: isAnonymous ? handleSignIn : undefined,
144
144
  accountSettingsRoute: isAnonymous ? undefined : "Account",
145
145
  };
@@ -155,7 +155,7 @@ export const useSettingsScreenConfig = (
155
155
  return {
156
156
  profile: {
157
157
  displayName: userProfileData?.displayName || user?.displayName || getTranslation("settings.profile.anonymousName", "Anonymous"),
158
- userId: userProfileData?.userId ?? user?.uid ?? null,
158
+ userId: userProfileData?.userId ?? user?.uid ?? undefined,
159
159
  isAnonymous,
160
160
  avatarUrl: userProfileData?.avatarUrl ?? user?.photoURL ?? undefined,
161
161
  benefits: isAnonymous ? [
@@ -80,10 +80,13 @@ export const SettingsScreen: React.FC<SettingsScreenProps> = ({
80
80
  [config]
81
81
  );
82
82
 
83
- // Memoize feature detection to prevent unnecessary recalculations
83
+ // Feature detection hook (must be called at top level)
84
+ const detectedFeatures = useFeatureDetection(normalizedConfig, navigation, featureOptions);
85
+
86
+ // Memoize features to prevent unnecessary recalculations
84
87
  const features = React.useMemo(
85
- () => useFeatureDetection(normalizedConfig, navigation, featureOptions),
86
- [normalizedConfig, navigation, featureOptions]
88
+ () => detectedFeatures,
89
+ [detectedFeatures]
87
90
  );
88
91
 
89
92
  // Determine if user profile should be shown (explicit prop takes priority, then config)
@@ -70,6 +70,7 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
70
70
  }) => {
71
71
  const { t } = useLocalization();
72
72
 
73
+ // Optimize: Only track individual feature flags instead of entire object
73
74
  const hasAnyFeatures = useMemo(() =>
74
75
  features.appearance ||
75
76
  features.language ||
@@ -84,7 +85,21 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
84
85
  features.wallet ||
85
86
  features.gamification ||
86
87
  customSections.length > 0,
87
- [features, customSections.length]
88
+ [
89
+ features.appearance,
90
+ features.language,
91
+ features.notifications,
92
+ features.about,
93
+ features.legal,
94
+ features.disclaimer,
95
+ features.feedback,
96
+ features.rating,
97
+ features.faqs,
98
+ features.subscription,
99
+ features.wallet,
100
+ features.gamification,
101
+ customSections.length,
102
+ ]
88
103
  );
89
104
 
90
105
  return (
@@ -1,6 +0,0 @@
1
- /**
2
- * Appearance Hooks
3
- */
4
-
5
- export { useAppearance } from "./useAppearance";
6
- export { useAppearanceActions } from "./useAppearanceActions";
@@ -1,2 +0,0 @@
1
- export { AppearanceScreen } from "./AppearanceScreen";
2
- export type { AppearanceScreenProps } from "./AppearanceScreen";