@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.
- package/package.json +1 -1
- package/src/domains/about/presentation/components/AboutSection.tsx +3 -3
- package/src/domains/appearance/hooks/useAppearanceActions.ts +8 -9
- package/src/domains/appearance/presentation/components/AppearanceSection.tsx +3 -3
- package/src/domains/appearance/presentation/components/ColorPicker.tsx +3 -1
- package/src/domains/feedback/presentation/components/SupportSection.tsx +1 -0
- package/src/domains/feedback/presentation/hooks/useFeedbackForm.ts +11 -3
- package/src/domains/gamification/components/GamificationScreen/AchievementsList.tsx +19 -7
- package/src/domains/legal/presentation/components/LegalSection.tsx +3 -3
- package/src/domains/localization/infrastructure/components/useLanguageNavigation.ts +3 -3
- package/src/domains/localization/infrastructure/hooks/useLanguageSelection.ts +11 -10
- package/src/domains/localization/infrastructure/hooks/useLocalization.ts +8 -5
- package/src/domains/localization/presentation/components/LanguageSection.tsx +3 -3
- package/src/domains/localization/presentation/providers/LocalizationManager.tsx +3 -0
- package/src/domains/notifications/infrastructure/hooks/useNotificationSettings.ts +10 -3
- package/src/domains/notifications/presentation/components/NotificationsSection.tsx +3 -3
- package/src/domains/notifications/presentation/hooks/useTimePicker.ts +14 -4
- package/src/domains/notifications/reminders/presentation/screens/ReminderListScreen.tsx +19 -8
- package/src/domains/rating/presentation/hooks/useAppRating.tsx +1 -0
- package/src/infrastructure/utils/memoUtils.ts +4 -4
- package/src/presentation/components/ErrorBoundary/SettingsErrorBoundary.tsx +105 -0
- package/src/presentation/components/ErrorBoundary/index.ts +9 -0
- package/src/presentation/hooks/useSettingsScreenConfig.ts +2 -2
- package/src/presentation/navigation/hooks/useSettingsNavigation.ts +28 -0
- package/src/presentation/navigation/hooks/useSettingsScreens.ts +10 -4
- package/src/presentation/screens/components/GamificationSettingsItem.tsx +3 -3
- package/src/presentation/screens/components/SettingsContent.tsx +0 -1
- package/src/presentation/screens/components/SubscriptionSettingsItem.tsx +3 -3
- package/src/presentation/screens/components/VideoTutorialSettingsItem.tsx +3 -3
- package/src/presentation/screens/components/WalletSettingsItem.tsx +3 -3
- package/src/presentation/screens/components/sections/FeatureSettingsSection.tsx +3 -3
- package/src/presentation/screens/components/sections/SupportSettingsSection.tsx +3 -3
- package/src/presentation/utils/config-creators/feature-configs.ts +0 -1
- package/src/presentation/utils/config-creators/types.ts +6 -1
- package/src/presentation/utils/faqTranslator.ts +2 -1
- package/src/presentation/utils/screenFactory.ts +4 -4
- package/src/presentation/utils/useAuthHandlers.ts +40 -3
- 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.
|
|
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 =
|
|
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
|
|
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
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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, ...
|
|
46
|
-
}, [
|
|
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
|
-
|
|
43
|
-
const
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
20
|
-
}, [
|
|
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 (!
|
|
32
|
-
|
|
34
|
+
if (!isInitialized) {
|
|
35
|
+
initialize();
|
|
33
36
|
}
|
|
34
|
-
}, [
|
|
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 =
|
|
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
|
|
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
|
-
//
|
|
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 =
|
|
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
|
|
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
|
-
|
|
53
|
+
// Use ref to get current mode without recreating callback
|
|
54
|
+
if (pickerModeRef.current === 'start') {
|
|
46
55
|
onStartTimeChange(hours, minutes);
|
|
47
|
-
} else if (
|
|
56
|
+
} else if (pickerModeRef.current === 'end') {
|
|
48
57
|
onEndTimeChange(hours, minutes);
|
|
49
58
|
}
|
|
50
59
|
}
|
|
51
60
|
setPickerMode(null);
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -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:
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
+
});
|
|
@@ -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, (
|
|
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:
|
|
23
|
-
legalProps:
|
|
24
|
-
notificationTranslations:
|
|
25
|
-
quietHoursTranslations:
|
|
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 =
|
|
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
|
|
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 =
|
|
13
|
+
const navigation = useSettingsNavigation();
|
|
14
14
|
|
|
15
15
|
const handlePress = React.useCallback(() => {
|
|
16
16
|
if (config.route) {
|
|
17
|
-
navigation.navigate(config.route as
|
|
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 =
|
|
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
|
|
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 =
|
|
13
|
+
const navigation = useSettingsNavigation();
|
|
14
14
|
|
|
15
15
|
const handlePress = React.useCallback(() => {
|
|
16
16
|
if (config.route) {
|
|
17
|
-
navigation.navigate(config.route as
|
|
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 =
|
|
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
|
|
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 =
|
|
20
|
+
const navigation = useSettingsNavigation();
|
|
21
21
|
|
|
22
22
|
const handleFAQPress = useCallback(() => {
|
|
23
|
-
navigation.navigate("FAQ"
|
|
23
|
+
navigation.navigate("FAQ");
|
|
24
24
|
}, [navigation]);
|
|
25
25
|
|
|
26
26
|
if (!(features.feedback || features.rating || features.faqs || features.videoTutorial)) return null;
|
|
@@ -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?:
|
|
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?:
|
|
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<
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
+
};
|