@umituz/react-native-settings 5.3.41 → 5.3.43

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.
@@ -18,25 +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
- }
31
- export interface FeatureRequestDataProvider {
32
- /** Fetch all feature requests */
33
- fetchRequests: () => Promise<FeatureRequestItem[]>;
34
- /** Fetch current user's votes */
35
- fetchUserVotes: (userId: string) => Promise<Record<string, VoteType>>;
36
- /** Vote on a feature request */
37
- onVote: (requestId: string, userId: string, type: VoteType) => Promise<void>;
38
- /** Submit a new feature request */
39
- onSubmit: (data: FeatureRequestSubmitData) => Promise<void>;
40
- /** Current user ID (can be anonymous UID) */
41
- userId: string | null;
42
- }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * useFeatureRequests Hook
3
+ * Internal hook — fetches, votes, submits feature requests via Firestore
4
+ * Works with both authenticated and anonymous users
5
+ */
6
+ import type { FeatureRequestItem, VoteType } from "../domain/entities/FeatureRequestEntity";
7
+ export interface UseFeatureRequestsResult {
8
+ requests: FeatureRequestItem[];
9
+ userVotes: Record<string, VoteType>;
10
+ isLoading: boolean;
11
+ vote: (requestId: string, type: VoteType) => Promise<void>;
12
+ submitRequest: (data: {
13
+ title: string;
14
+ description: string;
15
+ type: string;
16
+ rating?: number;
17
+ }) => Promise<void>;
18
+ reload: () => Promise<void>;
19
+ userId: string | null;
20
+ }
21
+ export declare function useFeatureRequests(): UseFeatureRequestsResult;
@@ -1,12 +1,2 @@
1
1
  import React from "react";
2
- import type { FeatureRequestDataProvider } from "../../domain/entities/FeatureRequestEntity";
3
- interface FeatureRequestScreenProps {
4
- config?: {
5
- translations?: Record<string, any>;
6
- dataProvider?: FeatureRequestDataProvider;
7
- onSubmit?: (data: any) => Promise<void>;
8
- };
9
- texts: any;
10
- }
11
- export declare const FeatureRequestScreen: React.FC<FeatureRequestScreenProps>;
12
- export {};
2
+ export declare const FeatureRequestScreen: React.FC<any>;
@@ -7,7 +7,6 @@
7
7
  */
8
8
  import type { SettingsConfig, SettingsTranslations } from "../screens/types";
9
9
  import type { FeedbackFormData } from "../utils/config-creators";
10
- import type { FeatureRequestDataProvider } from "../../domains/feedback/domain/entities/FeatureRequestEntity";
11
10
  import type { AppInfo, FAQData, UserProfileDisplay, AdditionalScreen, AccountConfig } from "../navigation/types";
12
11
  export interface SettingsFeatures {
13
12
  notifications?: boolean;
@@ -27,7 +26,6 @@ export interface UseSettingsScreenConfigParams {
27
26
  faqData?: FAQData;
28
27
  isPremium: boolean;
29
28
  onFeedbackSubmit: (data: FeedbackFormData) => Promise<void>;
30
- featureRequestDataProvider?: FeatureRequestDataProvider;
31
29
  additionalScreens?: AdditionalScreen[];
32
30
  features?: SettingsFeatures;
33
31
  translations?: SettingsTranslations;
@@ -4,7 +4,6 @@
4
4
  */
5
5
  import type { FeatureVisibility } from "./BaseTypes";
6
6
  import type { FeedbackType } from "../../../domains/feedback/domain/entities/FeedbackEntity";
7
- import type { FeatureRequestDataProvider } from "../../../domains/feedback/domain/entities/FeatureRequestEntity";
8
7
  import type { FAQCategory } from "../../../domains/faqs/domain/entities/FAQEntity";
9
8
  import type { SettingsStackParamList } from "../../navigation/types";
10
9
  /**
@@ -47,8 +46,6 @@ export interface FeedbackConfig {
47
46
  }) => Promise<void>;
48
47
  /** Custom handler to open feedback screen (overrides default modal) */
49
48
  onPress?: () => void;
50
- /** Data provider for feature request screen (fetching, voting, submitting) */
51
- dataProvider?: FeatureRequestDataProvider;
52
49
  }
53
50
  /**
54
51
  * FAQ Settings Configuration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-settings",
3
- "version": "5.3.41",
3
+ "version": "5.3.43",
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",
@@ -21,28 +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
- }
36
-
37
- export interface FeatureRequestDataProvider {
38
- /** Fetch all feature requests */
39
- fetchRequests: () => Promise<FeatureRequestItem[]>;
40
- /** Fetch current user's votes */
41
- fetchUserVotes: (userId: string) => Promise<Record<string, VoteType>>;
42
- /** Vote on a feature request */
43
- onVote: (requestId: string, userId: string, type: VoteType) => Promise<void>;
44
- /** Submit a new feature request */
45
- onSubmit: (data: FeatureRequestSubmitData) => Promise<void>;
46
- /** Current user ID (can be anonymous UID) */
47
- userId: string | null;
48
- }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * useFeatureRequests Hook
3
+ * Internal hook — fetches, votes, submits feature requests via Firestore
4
+ * Works with both authenticated and anonymous users
5
+ */
6
+
7
+ import { useState, useEffect, useCallback, useRef } from "react";
8
+ import { Platform } from "react-native";
9
+ import { useAuth } from "@umituz/react-native-auth";
10
+ import {
11
+ collection,
12
+ doc,
13
+ getDocs,
14
+ getDoc,
15
+ setDoc,
16
+ addDoc,
17
+ updateDoc,
18
+ deleteDoc,
19
+ increment,
20
+ orderBy,
21
+ query,
22
+ serverTimestamp,
23
+ } from "firebase/firestore";
24
+ import { getFirestore } from "@umituz/react-native-firebase";
25
+ import type {
26
+ FeatureRequestItem,
27
+ FeatureRequestStatus,
28
+ VoteType,
29
+ } from "../domain/entities/FeatureRequestEntity";
30
+
31
+ const COLLECTION = "feature_requests";
32
+
33
+ export interface UseFeatureRequestsResult {
34
+ requests: FeatureRequestItem[];
35
+ userVotes: Record<string, VoteType>;
36
+ isLoading: boolean;
37
+ vote: (requestId: string, type: VoteType) => Promise<void>;
38
+ submitRequest: (data: { title: string; description: string; type: string; rating?: number }) => Promise<void>;
39
+ reload: () => Promise<void>;
40
+ userId: string | null;
41
+ }
42
+
43
+ export function useFeatureRequests(): UseFeatureRequestsResult {
44
+ const { user } = useAuth();
45
+ const userId = user?.uid ?? null;
46
+
47
+ const [requests, setRequests] = useState<FeatureRequestItem[]>([]);
48
+ const [userVotes, setUserVotes] = useState<Record<string, VoteType>>({});
49
+ const [isLoading, setIsLoading] = useState(true);
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
+
58
+ const fetchAll = useCallback(async () => {
59
+ const db = getFirestore();
60
+ if (!db) {
61
+ setIsLoading(false);
62
+ return;
63
+ }
64
+
65
+ try {
66
+ setIsLoading(true);
67
+
68
+ const q = query(collection(db, COLLECTION), orderBy("votes", "desc"));
69
+ const snapshot = await getDocs(q);
70
+
71
+ const items: FeatureRequestItem[] = snapshot.docs.map((d) => {
72
+ const data = d.data();
73
+ return {
74
+ id: d.id,
75
+ title: data.title ?? "",
76
+ description: data.description ?? "",
77
+ type: data.type ?? "feature_request",
78
+ status: (data.status ?? "pending") as FeatureRequestStatus,
79
+ votes: data.votes ?? 0,
80
+ commentCount: data.commentCount ?? 0,
81
+ createdBy: data.createdBy ?? "",
82
+ isAnonymous: data.isAnonymous ?? false,
83
+ platform: data.platform ?? "unknown",
84
+ createdAt: data.createdAt?.toDate?.()?.toISOString?.() ?? new Date().toISOString(),
85
+ updatedAt: data.updatedAt?.toDate?.()?.toISOString?.() ?? new Date().toISOString(),
86
+ };
87
+ });
88
+
89
+ setRequests(items);
90
+
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) => {
94
+ const voteRef = doc(db, COLLECTION, reqDoc.id, "votes", userId);
95
+ const voteSnap = await getDoc(voteRef);
96
+ if (voteSnap.exists()) {
97
+ return [reqDoc.id, voteSnap.data().type as VoteType] as const;
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];
106
+ }
107
+ setUserVotes(votes);
108
+ } else {
109
+ setUserVotes({});
110
+ }
111
+ } catch (error) {
112
+ if (__DEV__) console.warn("[useFeatureRequests] Load failed:", error);
113
+ } finally {
114
+ setIsLoading(false);
115
+ }
116
+ }, [userId]);
117
+
118
+ useEffect(() => {
119
+ fetchAll();
120
+ }, [fetchAll]);
121
+
122
+ const vote = useCallback(async (requestId: string, type: VoteType) => {
123
+ if (!userId) return;
124
+
125
+ // Prevent rapid double-tap on the same request
126
+ if (votingInProgress.current.has(requestId)) return;
127
+ votingInProgress.current.add(requestId);
128
+
129
+ const db = getFirestore();
130
+ if (!db) {
131
+ votingInProgress.current.delete(requestId);
132
+ return;
133
+ }
134
+
135
+ // Read latest vote from ref (not stale closure)
136
+ const previousVote = userVotesRef.current[requestId] as VoteType | undefined;
137
+ const isUndo = previousVote === type;
138
+
139
+ // Optimistic UI
140
+ setUserVotes((prev) => {
141
+ const next = { ...prev };
142
+ if (isUndo) { delete next[requestId]; } else { next[requestId] = type; }
143
+ return next;
144
+ });
145
+ setRequests((prev) =>
146
+ prev.map((r) => {
147
+ if (r.id !== requestId) return r;
148
+ let delta = 0;
149
+ if (isUndo) delta = type === "up" ? -1 : 1;
150
+ else if (previousVote) delta = type === "up" ? 2 : -2;
151
+ else delta = type === "up" ? 1 : -1;
152
+ return { ...r, votes: r.votes + delta };
153
+ }),
154
+ );
155
+
156
+ try {
157
+ const voteRef = doc(db, COLLECTION, requestId, "votes", userId);
158
+ const requestRef = doc(db, COLLECTION, requestId);
159
+ const existing = await getDoc(voteRef);
160
+
161
+ if (existing.exists()) {
162
+ const prev = existing.data().type as VoteType;
163
+ if (prev === type) {
164
+ await deleteDoc(voteRef);
165
+ await updateDoc(requestRef, { votes: increment(type === "up" ? -1 : 1) });
166
+ } else {
167
+ await setDoc(voteRef, { type, votedAt: serverTimestamp() });
168
+ await updateDoc(requestRef, { votes: increment(type === "up" ? 2 : -2) });
169
+ }
170
+ } else {
171
+ await setDoc(voteRef, { type, votedAt: serverTimestamp() });
172
+ await updateDoc(requestRef, { votes: increment(type === "up" ? 1 : -1) });
173
+ }
174
+ } catch (error) {
175
+ if (__DEV__) console.warn("[useFeatureRequests] Vote failed:", error);
176
+ // Rollback using previous known state
177
+ setUserVotes((prev) => {
178
+ const next = { ...prev };
179
+ if (previousVote) next[requestId] = previousVote; else delete next[requestId];
180
+ return next;
181
+ });
182
+ fetchAll();
183
+ } finally {
184
+ votingInProgress.current.delete(requestId);
185
+ }
186
+ }, [userId, fetchAll]);
187
+
188
+ const submitRequest = useCallback(async (data: { title: string; description: string; type: string; rating?: number }) => {
189
+ const db = getFirestore();
190
+ if (!db) throw new Error("Firestore not available");
191
+ if (!userId) throw new Error("User not authenticated");
192
+
193
+ // Create the feature request
194
+ const docRef = await addDoc(collection(db, COLLECTION), {
195
+ title: data.title,
196
+ description: data.description,
197
+ type: data.type || "feature_request",
198
+ status: "pending",
199
+ votes: 1,
200
+ commentCount: 0,
201
+ createdBy: userId,
202
+ isAnonymous: user?.isAnonymous ?? false,
203
+ platform: Platform.OS,
204
+ rating: data.rating ?? null,
205
+ createdAt: serverTimestamp(),
206
+ updatedAt: serverTimestamp(),
207
+ });
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
+
215
+ await fetchAll();
216
+ }, [userId, user?.isAnonymous, fetchAll]);
217
+
218
+ return { requests, userVotes, isLoading, vote, submitRequest, reload: fetchAll, userId };
219
+ }
@@ -1,43 +1,25 @@
1
- import React, { useState, useEffect, useCallback } from "react";
1
+ import React, { useState, useCallback } from "react";
2
2
  import {
3
3
  View,
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";
17
15
  import { FeedbackModal } from "../components/FeedbackModal";
18
- import type { FeedbackType } from "../../domain/entities/FeedbackEntity";
19
- import type {
20
- FeatureRequestItem,
21
- FeatureRequestDataProvider,
22
- VoteType,
23
- } from "../../domain/entities/FeatureRequestEntity";
16
+ import { useFeatureRequests } from "../../infrastructure/useFeatureRequests";
17
+ import type { FeatureRequestItem, VoteType } from "../../domain/entities/FeatureRequestEntity";
24
18
 
25
- interface FeatureRequestScreenProps {
26
- config?: {
27
- translations?: Record<string, any>;
28
- dataProvider?: FeatureRequestDataProvider;
29
- onSubmit?: (data: any) => Promise<void>;
30
- };
31
- texts: any;
32
- }
33
-
34
- export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ config, texts }) => {
19
+ export const FeatureRequestScreen: React.FC<any> = ({ config, texts }) => {
35
20
  const tokens = useAppDesignTokens();
36
- const dataProvider = config?.dataProvider;
21
+ const { requests, userVotes, isLoading, vote, submitRequest, userId } = useFeatureRequests();
37
22
 
38
- const [requests, setRequests] = useState<FeatureRequestItem[]>([]);
39
- const [userVotes, setUserVotes] = useState<Record<string, VoteType>>({});
40
- const [isLoading, setIsLoading] = useState(true);
41
23
  const [activeTab, setActiveTab] = useState<'all' | 'my' | 'roadmap'>('all');
42
24
  const [isModalVisible, setIsModalVisible] = useState(false);
43
25
  const [isSubmitting, setIsSubmitting] = useState(false);
@@ -63,101 +45,22 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
63
45
  dismissed: t.status?.dismissed || "Dismissed",
64
46
  };
65
47
 
66
- const loadData = useCallback(async () => {
67
- if (!dataProvider) {
68
- setIsLoading(false);
69
- return;
70
- }
71
-
72
- try {
73
- setIsLoading(true);
74
- const [fetchedRequests, fetchedVotes] = await Promise.all([
75
- dataProvider.fetchRequests(),
76
- dataProvider.userId
77
- ? dataProvider.fetchUserVotes(dataProvider.userId)
78
- : Promise.resolve({} as Record<string, VoteType>),
79
- ]);
80
- setRequests(fetchedRequests);
81
- setUserVotes(fetchedVotes);
82
- } catch (error) {
83
- if (__DEV__) console.warn("[FeatureRequestScreen] Failed to load data:", error);
84
- } finally {
85
- setIsLoading(false);
86
- }
87
- }, [dataProvider]);
88
-
89
- useEffect(() => {
90
- loadData();
91
- }, [loadData]);
92
-
93
- const handleVote = useCallback(async (id: string, type: VoteType) => {
94
- if (!dataProvider?.userId || !dataProvider.onVote) return;
95
-
96
- const previousVote = userVotes[id];
97
- const isUndoVote = previousVote === type;
98
-
99
- // Optimistic update
100
- setUserVotes(prev => {
101
- const next = { ...prev };
102
- if (isUndoVote) {
103
- delete next[id];
104
- } else {
105
- next[id] = type;
106
- }
107
- return next;
108
- });
109
-
110
- setRequests(prev => prev.map(req => {
111
- if (req.id !== id) return req;
112
- let delta = 0;
113
- if (isUndoVote) {
114
- delta = type === 'up' ? -1 : 1;
115
- } else if (previousVote) {
116
- delta = type === 'up' ? 2 : -2;
117
- } else {
118
- delta = type === 'up' ? 1 : -1;
119
- }
120
- return { ...req, votes: req.votes + delta };
121
- }));
122
-
123
- try {
124
- await dataProvider.onVote(id, dataProvider.userId, type);
125
- } catch (error) {
126
- // Revert on failure
127
- setUserVotes(prev => {
128
- const next = { ...prev };
129
- if (previousVote) {
130
- next[id] = previousVote;
131
- } else {
132
- delete next[id];
133
- }
134
- return next;
135
- });
136
- loadData();
137
- if (__DEV__) console.warn("[FeatureRequestScreen] Vote failed:", error);
138
- }
139
- }, [dataProvider, userVotes, loadData]);
140
-
141
48
  const handleSubmit = useCallback(async (data: any) => {
142
- if (!dataProvider?.onSubmit) return;
143
-
144
49
  setIsSubmitting(true);
145
50
  try {
146
- await dataProvider.onSubmit({
51
+ await submitRequest({
147
52
  title: data.title || "New Request",
148
53
  description: data.description,
149
54
  type: data.type || "feature_request",
150
55
  rating: data.rating,
151
56
  });
152
57
  setIsModalVisible(false);
153
- // Reload data to get the new request from the database
154
- await loadData();
155
58
  } catch (error) {
156
59
  if (__DEV__) console.warn("[FeatureRequestScreen] Submit failed:", error);
157
60
  } finally {
158
61
  setIsSubmitting(false);
159
62
  }
160
- }, [dataProvider, loadData]);
63
+ }, [submitRequest]);
161
64
 
162
65
  const getStatusColor = (status: string) => {
163
66
  switch (status) {
@@ -173,36 +76,28 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
173
76
  const filteredRequests = (() => {
174
77
  switch (activeTab) {
175
78
  case 'my':
176
- return requests.filter(r => r.createdBy === dataProvider?.userId);
79
+ return requests.filter(r => r.createdBy === userId);
177
80
  case 'roadmap':
178
- return requests.filter(r => r.status === 'planned' || r.status === 'completed' || r.status === 'review');
81
+ return requests.filter(r => ['planned', 'completed', 'review'].includes(r.status));
179
82
  default:
180
83
  return requests;
181
84
  }
182
85
  })();
183
86
 
184
- const renderRequestItem = ({ item }: { item: FeatureRequestItem }) => {
87
+ const renderRequestCard = (item: FeatureRequestItem) => {
185
88
  const voted = userVotes[item.id] || null;
186
89
 
187
90
  return (
188
- <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 }]}>
189
92
  <View style={styles.voteColumn}>
190
- <TouchableOpacity onPress={() => handleVote(item.id, 'up')}>
191
- <AtomicIcon
192
- name="chevron-up"
193
- size="md"
194
- color={voted === 'up' ? "primary" : "textSecondary" as any}
195
- />
93
+ <TouchableOpacity onPress={() => vote(item.id, 'up')}>
94
+ <AtomicIcon name="chevron-up" size="md" color={voted === 'up' ? "primary" : "textSecondary" as any} />
196
95
  </TouchableOpacity>
197
96
  <AtomicText style={[styles.voteCount, { color: voted === 'up' ? tokens.colors.primary : tokens.colors.textPrimary }]}>
198
97
  {item.votes}
199
98
  </AtomicText>
200
- <TouchableOpacity onPress={() => handleVote(item.id, 'down')}>
201
- <AtomicIcon
202
- name="chevron-down"
203
- size="md"
204
- color={voted === 'down' ? "primary" : "textSecondary" as any}
205
- />
99
+ <TouchableOpacity onPress={() => vote(item.id, 'down')}>
100
+ <AtomicIcon name="chevron-down" size="md" color={voted === 'down' ? "primary" : "textSecondary" as any} />
206
101
  </TouchableOpacity>
207
102
  </View>
208
103
 
@@ -221,11 +116,9 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
221
116
  </AtomicText>
222
117
 
223
118
  <View style={styles.cardFooter}>
224
- <View style={styles.platformBadge}>
225
- <AtomicText style={[styles.platformText, { color: tokens.colors.textTertiary }]}>
226
- {item.platform}
227
- </AtomicText>
228
- </View>
119
+ <AtomicText style={[styles.platformText, { color: tokens.colors.textTertiary }]}>
120
+ {item.platform.toUpperCase()}
121
+ </AtomicText>
229
122
  <AtomicText style={[styles.commentCount, { color: tokens.colors.textTertiary }]}>
230
123
  {item.commentCount} {t.comment_count?.replace('{{count}}', '') || 'comments'}
231
124
  </AtomicText>
@@ -297,13 +190,9 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
297
190
  </AtomicText>
298
191
  </View>
299
192
  ) : (
300
- <FlatList
301
- data={filteredRequests}
302
- renderItem={renderRequestItem}
303
- keyExtractor={item => item.id}
304
- scrollEnabled={false}
305
- contentContainerStyle={styles.listContent}
306
- />
193
+ <View style={styles.listContent}>
194
+ {filteredRequests.map(renderRequestCard)}
195
+ </View>
307
196
  )}
308
197
  </ScrollView>
309
198
 
@@ -332,180 +221,36 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
332
221
  };
333
222
 
334
223
  const styles = StyleSheet.create({
335
- container: {
336
- padding: 16,
337
- },
338
- header: {
339
- flexDirection: 'row',
340
- alignItems: 'center',
341
- justifyContent: 'space-between',
342
- paddingHorizontal: 16,
343
- paddingVertical: 12,
344
- },
345
- headerTitle: {
346
- fontSize: 20,
347
- fontWeight: '800',
348
- },
349
- addButton: {
350
- width: 36,
351
- height: 36,
352
- borderRadius: 12,
353
- alignItems: 'center',
354
- justifyContent: 'center',
355
- },
356
- tabsContainer: {
357
- flexDirection: 'row',
358
- paddingHorizontal: 16,
359
- borderBottomWidth: 1,
360
- borderBottomColor: 'rgba(255,255,255,0.05)',
361
- },
362
- tab: {
363
- paddingVertical: 12,
364
- paddingHorizontal: 16,
365
- borderBottomWidth: 2,
366
- borderBottomColor: 'transparent',
367
- },
368
- tabLabel: {
369
- fontSize: 14,
370
- fontWeight: '600',
371
- color: 'rgba(255,255,255,0.5)',
372
- },
373
- banner: {
374
- flexDirection: 'row',
375
- alignItems: 'center',
376
- padding: 16,
377
- borderRadius: 16,
378
- borderWidth: 1,
379
- gap: 12,
380
- marginBottom: 20,
381
- },
382
- bannerIconContainer: {
383
- position: 'relative',
384
- },
385
- pulseDot: {
386
- position: 'absolute',
387
- top: 0,
388
- right: 0,
389
- width: 8,
390
- height: 8,
391
- borderRadius: 4,
392
- backgroundColor: '#10b981',
393
- },
394
- bannerTitle: {
395
- fontSize: 14,
396
- fontWeight: '700',
397
- },
398
- bannerSub: {
399
- fontSize: 12,
400
- },
401
- sectionTitle: {
402
- fontSize: 18,
403
- fontWeight: '700',
404
- marginBottom: 16,
405
- },
406
- listContent: {
407
- gap: 12,
408
- paddingBottom: 40,
409
- },
410
- card: {
411
- flexDirection: 'row',
412
- padding: 16,
413
- borderRadius: 16,
414
- borderWidth: 1,
415
- gap: 12,
416
- },
417
- voteColumn: {
418
- alignItems: 'center',
419
- gap: 4,
420
- width: 40,
421
- },
422
- voteCount: {
423
- fontSize: 13,
424
- fontWeight: '800',
425
- },
426
- cardContent: {
427
- flex: 1,
428
- gap: 8,
429
- },
430
- cardHeader: {
431
- flexDirection: 'row',
432
- justifyContent: 'space-between',
433
- alignItems: 'flex-start',
434
- gap: 8,
435
- },
436
- cardTitle: {
437
- fontSize: 15,
438
- fontWeight: '700',
439
- flex: 1,
440
- },
441
- statusBadge: {
442
- paddingHorizontal: 8,
443
- paddingVertical: 2,
444
- borderRadius: 8,
445
- borderWidth: 1,
446
- },
447
- statusText: {
448
- fontSize: 9,
449
- fontWeight: '900',
450
- },
451
- cardDescription: {
452
- fontSize: 13,
453
- lineHeight: 18,
454
- },
455
- cardFooter: {
456
- flexDirection: 'row',
457
- alignItems: 'center',
458
- justifyContent: 'space-between',
459
- marginTop: 4,
460
- },
461
- platformBadge: {
462
- paddingHorizontal: 6,
463
- paddingVertical: 2,
464
- },
465
- platformText: {
466
- fontSize: 10,
467
- fontWeight: '600',
468
- textTransform: 'uppercase',
469
- },
470
- commentCount: {
471
- fontSize: 11,
472
- },
473
- floatingHint: {
474
- position: 'absolute',
475
- bottom: 40,
476
- right: 16,
477
- zIndex: 100,
478
- },
479
- hintBadge: {
480
- paddingHorizontal: 12,
481
- paddingVertical: 6,
482
- borderRadius: 20,
483
- shadowColor: '#000',
484
- shadowOffset: { width: 0, height: 4 },
485
- shadowOpacity: 0.3,
486
- shadowRadius: 4,
487
- elevation: 8,
488
- },
489
- hintText: {
490
- color: '#fff',
491
- fontSize: 10,
492
- fontWeight: '900',
493
- textTransform: 'uppercase',
494
- },
495
- loadingContainer: {
496
- flex: 1,
497
- justifyContent: 'center',
498
- alignItems: 'center',
499
- paddingTop: 100,
500
- },
501
- emptyState: {
502
- alignItems: 'center',
503
- justifyContent: 'center',
504
- paddingVertical: 60,
505
- gap: 12,
506
- },
507
- emptyText: {
508
- fontSize: 14,
509
- fontWeight: '500',
510
- },
224
+ container: { padding: 16 },
225
+ header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16, paddingVertical: 12 },
226
+ headerTitle: { fontSize: 20, fontWeight: '800' },
227
+ addButton: { width: 36, height: 36, borderRadius: 12, alignItems: 'center', justifyContent: 'center' },
228
+ tabsContainer: { flexDirection: 'row', paddingHorizontal: 16, borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.05)' },
229
+ tab: { paddingVertical: 12, paddingHorizontal: 16, borderBottomWidth: 2, borderBottomColor: 'transparent' },
230
+ tabLabel: { fontSize: 14, fontWeight: '600', color: 'rgba(255,255,255,0.5)' },
231
+ banner: { flexDirection: 'row', alignItems: 'center', padding: 16, borderRadius: 16, borderWidth: 1, gap: 12, marginBottom: 20 },
232
+ bannerIconContainer: { position: 'relative' },
233
+ pulseDot: { position: 'absolute', top: 0, right: 0, width: 8, height: 8, borderRadius: 4, backgroundColor: '#10b981' },
234
+ bannerTitle: { fontSize: 14, fontWeight: '700' },
235
+ bannerSub: { fontSize: 12 },
236
+ sectionTitle: { fontSize: 18, fontWeight: '700', marginBottom: 16 },
237
+ listContent: { gap: 12, paddingBottom: 40 },
238
+ card: { flexDirection: 'row', padding: 16, borderRadius: 16, borderWidth: 1, gap: 12 },
239
+ voteColumn: { alignItems: 'center', gap: 4, width: 40 },
240
+ voteCount: { fontSize: 13, fontWeight: '800' },
241
+ cardContent: { flex: 1, gap: 8 },
242
+ cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 8 },
243
+ cardTitle: { fontSize: 15, fontWeight: '700', flex: 1 },
244
+ statusBadge: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 8, borderWidth: 1 },
245
+ statusText: { fontSize: 9, fontWeight: '900' },
246
+ cardDescription: { fontSize: 13, lineHeight: 18 },
247
+ cardFooter: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginTop: 4 },
248
+ platformText: { fontSize: 10, fontWeight: '600' },
249
+ commentCount: { fontSize: 11 },
250
+ floatingHint: { position: 'absolute', bottom: 40, right: 16, zIndex: 100 },
251
+ hintBadge: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, shadowRadius: 4, elevation: 8 },
252
+ hintText: { color: '#fff', fontSize: 10, fontWeight: '900', textTransform: 'uppercase' },
253
+ loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingTop: 100 },
254
+ emptyState: { alignItems: 'center', justifyContent: 'center', paddingVertical: 60, gap: 12 },
255
+ emptyText: { fontSize: 14, fontWeight: '500' },
511
256
  });
@@ -14,7 +14,6 @@ import { translateFAQData } from "../utils/faqTranslator";
14
14
  import { useSettingsConfigFactory } from "../utils/settingsConfigFactory";
15
15
  import type { SettingsConfig, SettingsTranslations } from "../screens/types";
16
16
  import type { FeedbackFormData } from "../utils/config-creators";
17
- import type { FeatureRequestDataProvider } from "../../domains/feedback/domain/entities/FeatureRequestEntity";
18
17
  import type { AppInfo, FAQData, UserProfileDisplay, AdditionalScreen, AccountConfig } from "../navigation/types";
19
18
 
20
19
  export interface SettingsFeatures {
@@ -36,7 +35,6 @@ export interface UseSettingsScreenConfigParams {
36
35
  faqData?: FAQData;
37
36
  isPremium: boolean;
38
37
  onFeedbackSubmit: (data: FeedbackFormData) => Promise<void>;
39
- featureRequestDataProvider?: FeatureRequestDataProvider;
40
38
  additionalScreens?: AdditionalScreen[];
41
39
  features?: SettingsFeatures;
42
40
  translations?: SettingsTranslations;
@@ -54,7 +52,7 @@ export interface SettingsScreenConfigResult {
54
52
  export const useSettingsScreenConfig = (
55
53
  params: UseSettingsScreenConfigParams
56
54
  ): SettingsScreenConfigResult => {
57
- const { appInfo, faqData, isPremium, onFeedbackSubmit, featureRequestDataProvider, features = {}, translations } = params;
55
+ const { appInfo, faqData, isPremium, onFeedbackSubmit, features = {}, translations } = params;
58
56
 
59
57
  const {
60
58
  notifications: showNotifications = true,
@@ -104,15 +102,6 @@ export const useSettingsScreenConfig = (
104
102
  translations,
105
103
  };
106
104
 
107
- // Merge dataProvider into feedback config
108
- if (featureRequestDataProvider && config.feedback) {
109
- const existingFeedback = typeof config.feedback === 'object' ? config.feedback : { enabled: true };
110
- config.feedback = {
111
- ...existingFeedback,
112
- dataProvider: featureRequestDataProvider,
113
- };
114
- }
115
-
116
105
  if (config.subscription && typeof config.subscription === 'object') {
117
106
  config.subscription = {
118
107
  ...config.subscription,
@@ -143,7 +132,7 @@ export const useSettingsScreenConfig = (
143
132
  }
144
133
 
145
134
  return config;
146
- }, [baseSettingsConfig, translations, featureRequestDataProvider]);
135
+ }, [baseSettingsConfig, translations]);
147
136
 
148
137
  const userProfile = useMemo(() => createUserProfileDisplay({
149
138
  profileData: userProfileData,
@@ -133,12 +133,9 @@ export const useSettingsScreens = (props: UseSettingsScreensProps): StackScreen[
133
133
  title: videoTutorialConfig?.title || featureTranslations?.videoTutorial?.title || "",
134
134
  });
135
135
 
136
- const feedbackConfig = typeof config?.feedback === 'object' ? config.feedback : undefined;
137
136
  const featureRequestScreen = createScreenWithProps("FeatureRequest", FeatureRequestScreen, {
138
137
  config: {
139
- ...feedbackConfig,
140
138
  translations: translations?.feedback,
141
- dataProvider: feedbackConfig?.dataProvider,
142
139
  },
143
140
  texts: {
144
141
  feedbackTypes: [
@@ -5,7 +5,6 @@
5
5
 
6
6
  import type { FeatureVisibility } from "./BaseTypes";
7
7
  import type { FeedbackType } from "../../../domains/feedback/domain/entities/FeedbackEntity";
8
- import type { FeatureRequestDataProvider } from "../../../domains/feedback/domain/entities/FeatureRequestEntity";
9
8
  import type { FAQCategory } from "../../../domains/faqs/domain/entities/FAQEntity";
10
9
  import type { SettingsStackParamList } from "../../navigation/types";
11
10
 
@@ -45,8 +44,6 @@ export interface FeedbackConfig {
45
44
  onSubmit?: (data: { type: FeedbackType; rating: number; description: string; title: string }) => Promise<void>;
46
45
  /** Custom handler to open feedback screen (overrides default modal) */
47
46
  onPress?: () => void;
48
- /** Data provider for feature request screen (fetching, voting, submitting) */
49
- dataProvider?: FeatureRequestDataProvider;
50
47
  }
51
48
 
52
49
  /**