@umituz/react-native-settings 4.16.4 → 4.16.6
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 +7 -1
- package/src/presentation/components/DevSettingsSection.tsx +2 -2
- package/src/presentation/components/FeedbackSupportSection.tsx +59 -0
- package/src/presentation/components/UserProfileHeader.tsx +10 -44
- package/src/presentation/screens/components/SettingsContent.tsx +17 -0
- package/src/presentation/screens/hooks/useFeatureDetection.ts +2 -0
- package/src/presentation/screens/types/FeatureConfig.ts +16 -0
- package/src/presentation/screens/types/SettingsConfig.ts +7 -0
- package/src/presentation/screens/types/index.ts +1 -0
- package/src/presentation/screens/utils/normalizeConfig.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-settings",
|
|
3
|
-
"version": "4.16.
|
|
3
|
+
"version": "4.16.6",
|
|
4
4
|
"description": "Settings management for React Native apps - user preferences, theme, language, notifications",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"@react-navigation/native": ">=6.0.0",
|
|
31
31
|
"@react-navigation/stack": ">=6.0.0",
|
|
32
32
|
"@umituz/react-native-about": "^1.11.3",
|
|
33
|
+
"@umituz/react-native-avatar": "*",
|
|
33
34
|
"@umituz/react-native-appearance": "*",
|
|
34
35
|
"@umituz/react-native-design-system": "*",
|
|
35
36
|
"@umituz/react-native-design-system-atoms": "*",
|
|
@@ -38,11 +39,14 @@
|
|
|
38
39
|
"@umituz/react-native-design-system-responsive": "*",
|
|
39
40
|
"@umituz/react-native-design-system-theme": "*",
|
|
40
41
|
"@umituz/react-native-design-system-typography": "*",
|
|
42
|
+
"@umituz/react-native-exception": "latest",
|
|
43
|
+
"@umituz/react-native-feedback": "latest",
|
|
41
44
|
"@umituz/react-native-legal": "*",
|
|
42
45
|
"@umituz/react-native-localization": "*",
|
|
43
46
|
"@umituz/react-native-notifications": "*",
|
|
44
47
|
"@umituz/react-native-onboarding": "*",
|
|
45
48
|
"@umituz/react-native-storage": "*",
|
|
49
|
+
"lucide-react-native": "*",
|
|
46
50
|
"react": ">=18.2.0",
|
|
47
51
|
"react-native": ">=0.74.0",
|
|
48
52
|
"react-native-safe-area-context": "~5.6.0",
|
|
@@ -64,6 +68,7 @@
|
|
|
64
68
|
"@umituz/react-native-about": "^1.11.4",
|
|
65
69
|
"@umituz/react-native-alert": "latest",
|
|
66
70
|
"@umituz/react-native-appearance": "^2.0.2",
|
|
71
|
+
"@umituz/react-native-avatar": "latest",
|
|
67
72
|
"@umituz/react-native-design-system": "latest",
|
|
68
73
|
"@umituz/react-native-design-system-atoms": "latest",
|
|
69
74
|
"@umituz/react-native-design-system-molecules": "latest",
|
|
@@ -71,6 +76,7 @@
|
|
|
71
76
|
"@umituz/react-native-design-system-responsive": "latest",
|
|
72
77
|
"@umituz/react-native-design-system-theme": "latest",
|
|
73
78
|
"@umituz/react-native-design-system-typography": "latest",
|
|
79
|
+
"@umituz/react-native-feedback": "latest",
|
|
74
80
|
"@umituz/react-native-legal": "^2.0.3",
|
|
75
81
|
"@umituz/react-native-localization": "latest",
|
|
76
82
|
"@umituz/react-native-notifications": "latest",
|
|
@@ -38,11 +38,11 @@ const DEFAULT_TEXTS = {
|
|
|
38
38
|
export interface DevSettingsProps {
|
|
39
39
|
/** Enable dev settings section (default: true in __DEV__ mode) */
|
|
40
40
|
enabled?: boolean;
|
|
41
|
-
/** Callback after storage is cleared - use this to reload the app */
|
|
41
|
+
/** Callback after storage is cleared - use this to reload the app and reset app state (e.g., onboarding) */
|
|
42
42
|
onAfterClear?: () => Promise<void>;
|
|
43
43
|
/** Custom texts (optional - defaults to English) */
|
|
44
44
|
texts?: Partial<typeof DEFAULT_TEXTS>;
|
|
45
|
-
/** Custom dev components to render
|
|
45
|
+
/** Custom dev components to render BEFORE the "Clear All Data" button (e.g., OnboardingResetSetting) */
|
|
46
46
|
customDevComponents?: React.ReactNode[];
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback Support Section Component
|
|
3
|
+
* Renders a Feedback Item and Modal
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useState } from "react";
|
|
7
|
+
import { SettingsSection } from "./SettingsSection";
|
|
8
|
+
import { SettingItem } from "./SettingItem";
|
|
9
|
+
import { FeedbackModal } from "@umituz/react-native-feedback";
|
|
10
|
+
import { MessageSquare } from "lucide-react-native";
|
|
11
|
+
import type { FeedbackConfig } from "../screens/types/FeatureConfig";
|
|
12
|
+
|
|
13
|
+
export interface FeedbackSupportSectionProps {
|
|
14
|
+
config: FeedbackConfig;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const FeedbackSupportSection: React.FC<FeedbackSupportSectionProps> = ({ config }) => {
|
|
18
|
+
const [modalVisible, setModalVisible] = useState(false);
|
|
19
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
20
|
+
|
|
21
|
+
const handleSubmit = async (data: { type: any; rating: number; description: string; title: string }) => {
|
|
22
|
+
if (config.onSubmit) {
|
|
23
|
+
setIsSubmitting(true);
|
|
24
|
+
try {
|
|
25
|
+
await config.onSubmit(data);
|
|
26
|
+
setModalVisible(false);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error("Feedback submission error:", error);
|
|
29
|
+
// Ideally show toast or alert here
|
|
30
|
+
} finally {
|
|
31
|
+
setIsSubmitting(false);
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
console.warn("No onSubmit handler provided for Feedback");
|
|
35
|
+
setModalVisible(false);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
<SettingsSection title={config.title || "Support"}>
|
|
42
|
+
<SettingItem
|
|
43
|
+
title={config.description || "Send Feedback"}
|
|
44
|
+
icon={MessageSquare as any}
|
|
45
|
+
onPress={() => setModalVisible(true)}
|
|
46
|
+
/>
|
|
47
|
+
</SettingsSection>
|
|
48
|
+
|
|
49
|
+
<FeedbackModal
|
|
50
|
+
visible={modalVisible}
|
|
51
|
+
onClose={() => setModalVisible(false)}
|
|
52
|
+
onSubmit={handleSubmit}
|
|
53
|
+
initialType={config.initialType}
|
|
54
|
+
isSubmitting={isSubmitting}
|
|
55
|
+
title={config.title ? undefined : "Send Feedback"} // Use config title if passed? No, modal title usually fixed or different.
|
|
56
|
+
/>
|
|
57
|
+
</>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
* Works for both guest and authenticated users
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import React, {
|
|
8
|
-
import { View, TouchableOpacity, StyleSheet
|
|
7
|
+
import React, { useCallback } from "react";
|
|
8
|
+
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
9
9
|
import { Feather } from "@expo/vector-icons";
|
|
10
10
|
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
11
11
|
import { AtomicText } from "@umituz/react-native-design-system-atoms";
|
|
12
12
|
import { useNavigation } from "@react-navigation/native";
|
|
13
|
+
import { Avatar } from "@umituz/react-native-avatar";
|
|
13
14
|
|
|
14
15
|
export interface UserProfileHeaderProps {
|
|
15
16
|
/** User display name */
|
|
@@ -50,16 +51,9 @@ export const UserProfileHeader: React.FC<UserProfileHeaderProps> = ({
|
|
|
50
51
|
const navigation = useNavigation();
|
|
51
52
|
const colors = tokens.colors;
|
|
52
53
|
const spacing = tokens.spacing;
|
|
53
|
-
const [imageError, setImageError] = useState(false);
|
|
54
|
-
|
|
55
54
|
const finalDisplayName = displayName || (isAnonymous ? anonymousDisplayName || defaultAnonymousDisplayName || "Anonymous" : defaultUserDisplayName || "User");
|
|
56
55
|
const avatarName = isAnonymous ? anonymousDisplayName || defaultAnonymousDisplayName || "Anonymous" : finalDisplayName;
|
|
57
56
|
|
|
58
|
-
const defaultAvatarService = avatarServiceUrl || "https://ui-avatars.com/api";
|
|
59
|
-
const finalAvatarUrl =
|
|
60
|
-
(imageError ? null : avatarUrl) ||
|
|
61
|
-
`${defaultAvatarService}/?name=${encodeURIComponent(avatarName)}&background=${colors.primary.replace("#", "")}&color=fff&size=64`;
|
|
62
|
-
|
|
63
57
|
const handlePress = useCallback(() => {
|
|
64
58
|
if (onPress) {
|
|
65
59
|
onPress();
|
|
@@ -84,24 +78,13 @@ export const UserProfileHeader: React.FC<UserProfileHeaderProps> = ({
|
|
|
84
78
|
const content = (
|
|
85
79
|
<>
|
|
86
80
|
<View style={styles.content}>
|
|
87
|
-
<View style={
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
) : (
|
|
95
|
-
<View style={[styles.avatarFallback, { backgroundColor: `${colors.primary}20` }]}>
|
|
96
|
-
<AtomicText
|
|
97
|
-
type="headlineMedium"
|
|
98
|
-
color="primary"
|
|
99
|
-
style={styles.avatarText}
|
|
100
|
-
>
|
|
101
|
-
{avatarName.charAt(0).toUpperCase()}
|
|
102
|
-
</AtomicText>
|
|
103
|
-
</View>
|
|
104
|
-
)}
|
|
81
|
+
<View style={styles.avatarContainer}>
|
|
82
|
+
<Avatar
|
|
83
|
+
uri={avatarUrl}
|
|
84
|
+
name={avatarName}
|
|
85
|
+
size="lg"
|
|
86
|
+
shape="circle"
|
|
87
|
+
/>
|
|
105
88
|
</View>
|
|
106
89
|
<View style={[styles.textContainer, { marginLeft: spacing.md }]}>
|
|
107
90
|
<AtomicText
|
|
@@ -161,26 +144,9 @@ const styles = StyleSheet.create({
|
|
|
161
144
|
flex: 1,
|
|
162
145
|
},
|
|
163
146
|
avatarContainer: {
|
|
164
|
-
width: 64,
|
|
165
|
-
height: 64,
|
|
166
|
-
borderRadius: 32,
|
|
167
|
-
borderWidth: 2,
|
|
168
|
-
overflow: "hidden",
|
|
169
|
-
backgroundColor: "transparent",
|
|
170
|
-
},
|
|
171
|
-
avatar: {
|
|
172
|
-
width: "100%",
|
|
173
|
-
height: "100%",
|
|
174
|
-
},
|
|
175
|
-
avatarFallback: {
|
|
176
|
-
width: "100%",
|
|
177
|
-
height: "100%",
|
|
178
147
|
justifyContent: "center",
|
|
179
148
|
alignItems: "center",
|
|
180
149
|
},
|
|
181
|
-
avatarText: {
|
|
182
|
-
fontWeight: "700",
|
|
183
|
-
},
|
|
184
150
|
textContainer: {
|
|
185
151
|
flex: 1,
|
|
186
152
|
},
|
|
@@ -17,6 +17,7 @@ import { AboutSection } from "@umituz/react-native-about";
|
|
|
17
17
|
import { LegalSection } from "@umituz/react-native-legal";
|
|
18
18
|
import { AppearanceSection } from "@umituz/react-native-appearance";
|
|
19
19
|
import { LanguageSection } from "@umituz/react-native-localization";
|
|
20
|
+
import { FeedbackSupportSection } from "../../components/FeedbackSupportSection";
|
|
20
21
|
import type { NormalizedConfig } from "../utils/normalizeConfig";
|
|
21
22
|
import type { CustomSettingsSection } from "../types";
|
|
22
23
|
|
|
@@ -56,6 +57,7 @@ interface SettingsContentProps {
|
|
|
56
57
|
disclaimer: boolean;
|
|
57
58
|
userProfile: boolean;
|
|
58
59
|
subscription: boolean;
|
|
60
|
+
feedback: boolean;
|
|
59
61
|
};
|
|
60
62
|
showUserProfile?: boolean;
|
|
61
63
|
userProfile?: {
|
|
@@ -102,6 +104,7 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
|
|
|
102
104
|
features.legal ||
|
|
103
105
|
features.disclaimer ||
|
|
104
106
|
features.subscription ||
|
|
107
|
+
features.feedback ||
|
|
105
108
|
customSections.length > 0,
|
|
106
109
|
[features, customSections.length]
|
|
107
110
|
);
|
|
@@ -215,6 +218,20 @@ export const SettingsContent: React.FC<SettingsContentProps> = ({
|
|
|
215
218
|
/>
|
|
216
219
|
)}
|
|
217
220
|
|
|
221
|
+
{features.feedback && (
|
|
222
|
+
<FeedbackSupportSection
|
|
223
|
+
config={{
|
|
224
|
+
...normalizedConfig.feedback.config,
|
|
225
|
+
title:
|
|
226
|
+
normalizedConfig.feedback.config?.title ||
|
|
227
|
+
t("settings.feedback.title") || "Support",
|
|
228
|
+
description:
|
|
229
|
+
normalizedConfig.feedback.config?.description ||
|
|
230
|
+
t("settings.feedback.description") || "Send Feedback",
|
|
231
|
+
}}
|
|
232
|
+
/>
|
|
233
|
+
)}
|
|
234
|
+
|
|
218
235
|
{features.disclaimer && DisclaimerSetting && (
|
|
219
236
|
<DisclaimerSetting />
|
|
220
237
|
)}
|
|
@@ -70,6 +70,7 @@ export function useFeatureDetection(
|
|
|
70
70
|
disclaimer,
|
|
71
71
|
userProfile,
|
|
72
72
|
subscription,
|
|
73
|
+
feedback,
|
|
73
74
|
} = normalizedConfig;
|
|
74
75
|
|
|
75
76
|
const notificationServiceAvailable =
|
|
@@ -123,6 +124,7 @@ export function useFeatureDetection(
|
|
|
123
124
|
subscription:
|
|
124
125
|
subscription.enabled &&
|
|
125
126
|
subscription.config?.sectionConfig !== undefined,
|
|
127
|
+
feedback: feedback.enabled,
|
|
126
128
|
};
|
|
127
129
|
}, [normalizedConfig, navigation, options]);
|
|
128
130
|
}
|
|
@@ -168,3 +168,19 @@ export interface SubscriptionConfig {
|
|
|
168
168
|
onUpgrade?: () => void;
|
|
169
169
|
};
|
|
170
170
|
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Feedback Settings Configuration
|
|
174
|
+
*/
|
|
175
|
+
export interface FeedbackConfig {
|
|
176
|
+
/** Show feedback section */
|
|
177
|
+
enabled?: FeatureVisibility;
|
|
178
|
+
/** Custom feedback title */
|
|
179
|
+
title?: string;
|
|
180
|
+
/** Custom feedback description */
|
|
181
|
+
description?: string;
|
|
182
|
+
/** Initial feedback type */
|
|
183
|
+
initialType?: "general" | "bug_report" | "feature_request" | "improvement" | "other";
|
|
184
|
+
/** Submit handler */
|
|
185
|
+
onSubmit?: (data: { type: string; rating: number; description: string; title: string }) => Promise<void>;
|
|
186
|
+
}
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
DisclaimerConfig,
|
|
14
14
|
UserProfileConfig,
|
|
15
15
|
SubscriptionConfig,
|
|
16
|
+
FeedbackConfig,
|
|
16
17
|
} from "./FeatureConfig";
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -96,6 +97,12 @@ export interface SettingsConfig {
|
|
|
96
97
|
*/
|
|
97
98
|
subscription?: FeatureVisibility | SubscriptionConfig;
|
|
98
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Feedback settings
|
|
102
|
+
* @default 'auto'
|
|
103
|
+
*/
|
|
104
|
+
feedback?: FeatureVisibility | FeedbackConfig;
|
|
105
|
+
|
|
99
106
|
/**
|
|
100
107
|
* Custom empty state text when no settings are available
|
|
101
108
|
*/
|
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
DisclaimerConfig,
|
|
14
14
|
UserProfileConfig,
|
|
15
15
|
SubscriptionConfig,
|
|
16
|
+
FeedbackConfig,
|
|
16
17
|
SettingsConfig,
|
|
17
18
|
} from "../types";
|
|
18
19
|
|
|
@@ -49,6 +50,10 @@ export interface NormalizedConfig {
|
|
|
49
50
|
enabled: boolean;
|
|
50
51
|
config?: SubscriptionConfig;
|
|
51
52
|
};
|
|
53
|
+
feedback: {
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
config?: FeedbackConfig;
|
|
56
|
+
};
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
/**
|
|
@@ -91,6 +96,7 @@ export function normalizeSettingsConfig(
|
|
|
91
96
|
disclaimer: normalizeConfigValue(config?.disclaimer, false),
|
|
92
97
|
userProfile: normalizeConfigValue(config?.userProfile, false),
|
|
93
98
|
subscription: normalizeConfigValue(config?.subscription, false),
|
|
99
|
+
feedback: normalizeConfigValue(config?.feedback, false),
|
|
94
100
|
};
|
|
95
101
|
}
|
|
96
102
|
|