@umituz/react-native-settings 5.3.42 → 5.3.44

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/dist/account.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Usage:
8
8
  * import {
9
- * useSettingsScreenConfigWithAuth,
9
+ * useSettingsScreenConfig,
10
10
  * AccountScreen,
11
11
  * ProfileSection,
12
12
  * } from '@umituz/react-native-settings/account';
@@ -22,6 +22,5 @@ export { AccountScreen, ProfileSection } from "@umituz/react-native-auth";
22
22
  export { useAuth, useUserProfile, useAuthHandlers } from "@umituz/react-native-auth";
23
23
  export type { AccountScreenConfig } from "@umituz/react-native-auth";
24
24
  export { useSettingsScreenConfig } from './presentation/hooks/useSettingsScreenConfig';
25
- export { useSettingsScreenConfig as useSettingsScreenConfigWithAuth } from './presentation/hooks/useSettingsScreenConfig';
26
25
  export type { UseSettingsScreenConfigParams, SettingsScreenConfigResult, SettingsFeatures, } from './presentation/hooks/useSettingsScreenConfig';
27
26
  export type { AccountConfig } from './presentation/navigation/types';
@@ -18,13 +18,3 @@ export interface FeatureRequestItem {
18
18
  createdAt: string;
19
19
  updatedAt: string;
20
20
  }
21
- export interface FeatureRequestVote {
22
- type: VoteType;
23
- votedAt: string;
24
- }
25
- export interface FeatureRequestSubmitData {
26
- title: string;
27
- description: string;
28
- type: string;
29
- rating?: number;
30
- }
@@ -2,24 +2,4 @@
2
2
  * Feedback Entity
3
3
  */
4
4
  export type FeedbackType = 'general' | 'bug_report' | 'feature_request' | 'improvement' | 'other';
5
- export type FeedbackStatus = 'pending' | 'reviewed' | 'resolved' | 'closed';
6
5
  export type FeedbackRating = 1 | 2 | 3 | 4 | 5;
7
- export interface FeedbackEntity {
8
- id: string;
9
- userId: string | null;
10
- userEmail?: string | null;
11
- type: FeedbackType;
12
- title: string;
13
- description: string;
14
- rating?: FeedbackRating;
15
- status: FeedbackStatus;
16
- deviceInfo?: {
17
- platform: string;
18
- osVersion: string;
19
- appVersion: string;
20
- };
21
- metadata?: Record<string, unknown>;
22
- createdAt: string;
23
- updatedAt: string;
24
- }
25
- export declare function createFeedback(userId: string | null, type: FeedbackType, title: string, description: string, userEmail?: string | null, rating?: FeedbackRating, deviceInfo?: FeedbackEntity['deviceInfo'], metadata?: Record<string, unknown>): Omit<FeedbackEntity, 'id'>;
@@ -8,4 +8,3 @@ export * from './presentation/components/SupportSection';
8
8
  export * from './presentation/hooks/useFeedbackForm';
9
9
  export * from './domain/entities/FeedbackEntity';
10
10
  export * from './domain/entities/FeatureRequestEntity';
11
- export * from './domain/repositories/IFeedbackRepository';
@@ -4,5 +4,4 @@
4
4
  export * from "./config-creators";
5
5
  export * from "./userProfileUtils";
6
6
  export * from "./accountConfigUtils";
7
- export * from "./faqTranslator";
8
7
  export * from "./settingsConfigFactory";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-settings",
3
- "version": "5.3.42",
3
+ "version": "5.3.44",
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": "./dist/index.d.ts",
package/src/account.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Usage:
8
8
  * import {
9
- * useSettingsScreenConfigWithAuth,
9
+ * useSettingsScreenConfig,
10
10
  * AccountScreen,
11
11
  * ProfileSection,
12
12
  * } from '@umituz/react-native-settings/account';
@@ -31,9 +31,6 @@ export type { AccountScreenConfig } from "@umituz/react-native-auth";
31
31
  // Base hook (already handles auth internally via useAuth/useUserProfile/useAuthHandlers)
32
32
  export { useSettingsScreenConfig } from './presentation/hooks/useSettingsScreenConfig';
33
33
 
34
- // Alias for clarity - useSettingsScreenConfig already includes auth
35
- export { useSettingsScreenConfig as useSettingsScreenConfigWithAuth } from './presentation/hooks/useSettingsScreenConfig';
36
-
37
34
  // Re-export types
38
35
  export type {
39
36
  UseSettingsScreenConfigParams,
@@ -16,7 +16,7 @@ Provides components and utilities for collecting user feedback including feedbac
16
16
 
17
17
  **Domain:**
18
18
  - `/Users/umituz/Desktop/github/umituz/apps/artificial_intelligence/npm-packages/react-native-settings/src/domains/feedback/domain/entities/FeedbackEntity.ts`
19
- - `/Users/umituz/Desktop/github/umituz/apps/artificial_intelligence/npm-packages/react-native-settings/src/domains/feedback/domain/repositories/IFeedbackRepository.ts`
19
+ - `/Users/umituz/Desktop/github/umituz/apps/artificial_intelligence/npm-packages/react-native-settings/src/domains/feedback/domain/entities/FeatureRequestEntity.ts`
20
20
 
21
21
  **Index:**
22
22
  - `/Users/umituz/Desktop/github/umituz/apps/artificial_intelligence/npm-packages/react-native-settings/src/domains/feedback/index.ts`
@@ -26,14 +26,12 @@ Provides components and utilities for collecting user feedback including feedbac
26
26
  1. **Form State Management**: Use useFeedbackForm hook for centralized form state and validation
27
27
  2. **Type Categorization**: Categorize feedback by type (bug, feature, general, etc.) for better routing
28
28
  3. **Star Rating Integration**: Integrate with Rating domain for 1-5 star ratings
29
- 4. **Repository Pattern**: Use IFeedbackRepository interface for abstracted feedback submission
30
- 5. **Validation First**: Always validate form state before submission to prevent invalid data
29
+ 4. **Validation First**: Always validate form state before submission to prevent invalid data
31
30
 
32
31
  ## Restrictions (Forbidden)
33
32
 
34
33
  ### DO NOT
35
34
  - ❌ DO NOT submit feedback without validating required fields (type, title)
36
- - ❌ DO NOT bypass the repository pattern when submitting feedback
37
35
  - ❌ DO NOT use FeedbackForm without providing texts configuration
38
36
  - ❌ DO NOT handle feedback submission manually when the hook provides everything needed
39
37
 
@@ -21,15 +21,3 @@ export interface FeatureRequestItem {
21
21
  createdAt: string;
22
22
  updatedAt: string;
23
23
  }
24
-
25
- export interface FeatureRequestVote {
26
- type: VoteType;
27
- votedAt: string;
28
- }
29
-
30
- export interface FeatureRequestSubmitData {
31
- title: string;
32
- description: string;
33
- type: string;
34
- rating?: number;
35
- }
@@ -9,51 +9,4 @@ export type FeedbackType =
9
9
  | 'improvement'
10
10
  | 'other';
11
11
 
12
- export type FeedbackStatus = 'pending' | 'reviewed' | 'resolved' | 'closed';
13
-
14
12
  export type FeedbackRating = 1 | 2 | 3 | 4 | 5;
15
-
16
- export interface FeedbackEntity {
17
- id: string;
18
- userId: string | null;
19
- userEmail?: string | null;
20
- type: FeedbackType;
21
- title: string;
22
- description: string;
23
- rating?: FeedbackRating;
24
- status: FeedbackStatus;
25
- deviceInfo?: {
26
- platform: string;
27
- osVersion: string;
28
- appVersion: string;
29
- };
30
- metadata?: Record<string, unknown>;
31
- createdAt: string;
32
- updatedAt: string;
33
- }
34
-
35
- export function createFeedback(
36
- userId: string | null,
37
- type: FeedbackType,
38
- title: string,
39
- description: string,
40
- userEmail?: string | null,
41
- rating?: FeedbackRating,
42
- deviceInfo?: FeedbackEntity['deviceInfo'],
43
- metadata?: Record<string, unknown>
44
- ): Omit<FeedbackEntity, 'id'> {
45
- const now = new Date().toISOString();
46
- return {
47
- userId,
48
- userEmail,
49
- type,
50
- title,
51
- description,
52
- rating,
53
- status: 'pending',
54
- deviceInfo,
55
- metadata,
56
- createdAt: now,
57
- updatedAt: now,
58
- };
59
- }
@@ -9,4 +9,3 @@ export * from './presentation/components/SupportSection';
9
9
  export * from './presentation/hooks/useFeedbackForm';
10
10
  export * from './domain/entities/FeedbackEntity';
11
11
  export * from './domain/entities/FeatureRequestEntity';
12
- export * from './domain/repositories/IFeedbackRepository';
@@ -4,7 +4,7 @@
4
4
  * Works with both authenticated and anonymous users
5
5
  */
6
6
 
7
- import { useState, useEffect, useCallback } from "react";
7
+ import { useState, useEffect, useCallback, useRef } from "react";
8
8
  import { Platform } from "react-native";
9
9
  import { useAuth } from "@umituz/react-native-auth";
10
10
  import {
@@ -48,6 +48,13 @@ export function useFeatureRequests(): UseFeatureRequestsResult {
48
48
  const [userVotes, setUserVotes] = useState<Record<string, VoteType>>({});
49
49
  const [isLoading, setIsLoading] = useState(true);
50
50
 
51
+ // Ref to avoid stale closure in vote()
52
+ const userVotesRef = useRef(userVotes);
53
+ userVotesRef.current = userVotes;
54
+
55
+ // Guard against rapid double-tap on the same request
56
+ const votingInProgress = useRef(new Set<string>());
57
+
51
58
  const fetchAll = useCallback(async () => {
52
59
  const db = getFirestore();
53
60
  if (!db) {
@@ -58,7 +65,6 @@ export function useFeatureRequests(): UseFeatureRequestsResult {
58
65
  try {
59
66
  setIsLoading(true);
60
67
 
61
- // Fetch requests
62
68
  const q = query(collection(db, COLLECTION), orderBy("votes", "desc"));
63
69
  const snapshot = await getDocs(q);
64
70
 
@@ -82,17 +88,25 @@ export function useFeatureRequests(): UseFeatureRequestsResult {
82
88
 
83
89
  setRequests(items);
84
90
 
85
- // Fetch user votes
86
- if (userId) {
87
- const votes: Record<string, VoteType> = {};
88
- for (const reqDoc of snapshot.docs) {
91
+ // Fetch user votes in parallel (not N+1 sequential)
92
+ if (userId && snapshot.docs.length > 0) {
93
+ const votePromises = snapshot.docs.map(async (reqDoc) => {
89
94
  const voteRef = doc(db, COLLECTION, reqDoc.id, "votes", userId);
90
95
  const voteSnap = await getDoc(voteRef);
91
96
  if (voteSnap.exists()) {
92
- votes[reqDoc.id] = voteSnap.data().type as VoteType;
97
+ return [reqDoc.id, voteSnap.data().type as VoteType] as const;
93
98
  }
99
+ return null;
100
+ });
101
+
102
+ const results = await Promise.all(votePromises);
103
+ const votes: Record<string, VoteType> = {};
104
+ for (const result of results) {
105
+ if (result) votes[result[0]] = result[1];
94
106
  }
95
107
  setUserVotes(votes);
108
+ } else {
109
+ setUserVotes({});
96
110
  }
97
111
  } catch (error) {
98
112
  if (__DEV__) console.warn("[useFeatureRequests] Load failed:", error);
@@ -108,10 +122,18 @@ export function useFeatureRequests(): UseFeatureRequestsResult {
108
122
  const vote = useCallback(async (requestId: string, type: VoteType) => {
109
123
  if (!userId) return;
110
124
 
125
+ // Prevent rapid double-tap on the same request
126
+ if (votingInProgress.current.has(requestId)) return;
127
+ votingInProgress.current.add(requestId);
128
+
111
129
  const db = getFirestore();
112
- if (!db) return;
130
+ if (!db) {
131
+ votingInProgress.current.delete(requestId);
132
+ return;
133
+ }
113
134
 
114
- const previousVote = userVotes[requestId];
135
+ // Read latest vote from ref (not stale closure)
136
+ const previousVote = userVotesRef.current[requestId] as VoteType | undefined;
115
137
  const isUndo = previousVote === type;
116
138
 
117
139
  // Optimistic UI
@@ -150,23 +172,26 @@ export function useFeatureRequests(): UseFeatureRequestsResult {
150
172
  await updateDoc(requestRef, { votes: increment(type === "up" ? 1 : -1) });
151
173
  }
152
174
  } catch (error) {
153
- // Rollback
154
175
  if (__DEV__) console.warn("[useFeatureRequests] Vote failed:", error);
176
+ // Rollback using previous known state
155
177
  setUserVotes((prev) => {
156
178
  const next = { ...prev };
157
179
  if (previousVote) next[requestId] = previousVote; else delete next[requestId];
158
180
  return next;
159
181
  });
160
182
  fetchAll();
183
+ } finally {
184
+ votingInProgress.current.delete(requestId);
161
185
  }
162
- }, [userId, userVotes, fetchAll]);
186
+ }, [userId, fetchAll]);
163
187
 
164
188
  const submitRequest = useCallback(async (data: { title: string; description: string; type: string; rating?: number }) => {
165
189
  const db = getFirestore();
166
190
  if (!db) throw new Error("Firestore not available");
167
191
  if (!userId) throw new Error("User not authenticated");
168
192
 
169
- await addDoc(collection(db, COLLECTION), {
193
+ // Create the feature request
194
+ const docRef = await addDoc(collection(db, COLLECTION), {
170
195
  title: data.title,
171
196
  description: data.description,
172
197
  type: data.type || "feature_request",
@@ -181,6 +206,12 @@ export function useFeatureRequests(): UseFeatureRequestsResult {
181
206
  updatedAt: serverTimestamp(),
182
207
  });
183
208
 
209
+ // Create the creator's upvote doc so votes count matches reality
210
+ await setDoc(doc(db, COLLECTION, docRef.id, "votes", userId), {
211
+ type: "up" as VoteType,
212
+ votedAt: serverTimestamp(),
213
+ });
214
+
184
215
  await fetchAll();
185
216
  }, [userId, user?.isAnonymous, fetchAll]);
186
217
 
@@ -4,13 +4,11 @@ import {
4
4
  StyleSheet,
5
5
  TouchableOpacity,
6
6
  ScrollView,
7
- FlatList,
8
7
  ActivityIndicator,
9
8
  } from "react-native";
10
9
  import {
11
10
  AtomicText,
12
11
  AtomicIcon,
13
- AtomicButton,
14
12
  } from "@umituz/react-native-design-system/atoms";
15
13
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
16
14
  import { ScreenLayout } from "@umituz/react-native-design-system/layouts";
@@ -47,10 +45,6 @@ export const FeatureRequestScreen: React.FC<any> = ({ config, texts }) => {
47
45
  dismissed: t.status?.dismissed || "Dismissed",
48
46
  };
49
47
 
50
- const handleVote = useCallback(async (id: string, type: VoteType) => {
51
- await vote(id, type);
52
- }, [vote]);
53
-
54
48
  const handleSubmit = useCallback(async (data: any) => {
55
49
  setIsSubmitting(true);
56
50
  try {
@@ -90,19 +84,19 @@ export const FeatureRequestScreen: React.FC<any> = ({ config, texts }) => {
90
84
  }
91
85
  })();
92
86
 
93
- const renderRequestItem = ({ item }: { item: FeatureRequestItem }) => {
87
+ const renderRequestCard = (item: FeatureRequestItem) => {
94
88
  const voted = userVotes[item.id] || null;
95
89
 
96
90
  return (
97
- <View style={[styles.card, { backgroundColor: tokens.colors.surfaceSecondary, borderColor: tokens.colors.borderLight }]}>
91
+ <View key={item.id} style={[styles.card, { backgroundColor: tokens.colors.surfaceSecondary, borderColor: tokens.colors.borderLight }]}>
98
92
  <View style={styles.voteColumn}>
99
- <TouchableOpacity onPress={() => handleVote(item.id, 'up')}>
93
+ <TouchableOpacity onPress={() => vote(item.id, 'up')}>
100
94
  <AtomicIcon name="chevron-up" size="md" color={voted === 'up' ? "primary" : "textSecondary" as any} />
101
95
  </TouchableOpacity>
102
96
  <AtomicText style={[styles.voteCount, { color: voted === 'up' ? tokens.colors.primary : tokens.colors.textPrimary }]}>
103
97
  {item.votes}
104
98
  </AtomicText>
105
- <TouchableOpacity onPress={() => handleVote(item.id, 'down')}>
99
+ <TouchableOpacity onPress={() => vote(item.id, 'down')}>
106
100
  <AtomicIcon name="chevron-down" size="md" color={voted === 'down' ? "primary" : "textSecondary" as any} />
107
101
  </TouchableOpacity>
108
102
  </View>
@@ -196,13 +190,9 @@ export const FeatureRequestScreen: React.FC<any> = ({ config, texts }) => {
196
190
  </AtomicText>
197
191
  </View>
198
192
  ) : (
199
- <FlatList
200
- data={filteredRequests}
201
- renderItem={renderRequestItem}
202
- keyExtractor={item => item.id}
203
- scrollEnabled={false}
204
- contentContainerStyle={styles.listContent}
205
- />
193
+ <View style={styles.listContent}>
194
+ {filteredRequests.map(renderRequestCard)}
195
+ </View>
206
196
  )}
207
197
  </ScrollView>
208
198
 
@@ -10,7 +10,6 @@ import { useMemo } from "react";
10
10
  import { useAuth, useUserProfile, useAuthHandlers } from "@umituz/react-native-auth";
11
11
  import { createUserProfileDisplay } from "../utils/userProfileUtils";
12
12
  import { createAccountConfig } from "../utils/accountConfigUtils";
13
- import { translateFAQData } from "../utils/faqTranslator";
14
13
  import { useSettingsConfigFactory } from "../utils/settingsConfigFactory";
15
14
  import type { SettingsConfig, SettingsTranslations } from "../screens/types";
16
15
  import type { FeedbackFormData } from "../utils/config-creators";
@@ -151,16 +150,11 @@ export const useSettingsScreenConfig = (
151
150
  translations: translations?.account as any,
152
151
  }), [user, userProfileData, handleSignIn, handleSignOut, handleDeleteAccount, translations]);
153
152
 
154
- const translatedFaqData = useMemo(() =>
155
- translateFAQData(faqData, appInfo),
156
- [faqData, appInfo]
157
- );
158
-
159
153
  return {
160
154
  settingsConfig,
161
155
  userProfile,
162
156
  accountConfig,
163
- translatedFaqData,
157
+ translatedFaqData: faqData,
164
158
  isLoading: loading,
165
159
  isAuthReady,
166
160
  };
@@ -5,5 +5,4 @@
5
5
  export * from "./config-creators";
6
6
  export * from "./userProfileUtils";
7
7
  export * from "./accountConfigUtils";
8
- export * from "./faqTranslator";
9
8
  export * from "./settingsConfigFactory";
@@ -1,28 +0,0 @@
1
- /**
2
- * Feedback Repository Interface
3
- */
4
-
5
- import type { FeedbackEntity } from '../entities/FeedbackEntity';
6
-
7
- export interface FeedbackError {
8
- message: string;
9
- code?: 'SUBMIT_FAILED' | 'FETCH_FAILED' | 'DELETE_FAILED' | 'VALIDATION_ERROR';
10
- }
11
-
12
- export type FeedbackResult<T> =
13
- | {
14
- success: true;
15
- data: T;
16
- }
17
- | {
18
- success: false;
19
- error: FeedbackError;
20
- };
21
-
22
- export interface IFeedbackRepository {
23
- submitFeedback(
24
- feedback: FeedbackEntity | Omit<FeedbackEntity, 'id'>
25
- ): Promise<FeedbackResult<FeedbackEntity>>;
26
- getUserFeedback(userId: string): Promise<FeedbackResult<FeedbackEntity[]>>;
27
- deleteFeedback(feedbackId: string): Promise<FeedbackResult<boolean>>;
28
- }
@@ -1,9 +0,0 @@
1
- import type { FAQData } from "../navigation/types";
2
- import type { AppInfo } from "../navigation/types";
3
-
4
- export const translateFAQData = (
5
- faqData: FAQData | undefined,
6
- _appInfo: AppInfo
7
- ): FAQData | undefined => {
8
- return faqData;
9
- };