@umituz/react-native-settings 5.3.41 → 5.3.42

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.
@@ -28,15 +28,3 @@ export interface FeatureRequestSubmitData {
28
28
  type: string;
29
29
  rating?: number;
30
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.42",
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",
@@ -33,16 +33,3 @@ export interface FeatureRequestSubmitData {
33
33
  type: string;
34
34
  rating?: number;
35
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,188 @@
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 } 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
+ const fetchAll = useCallback(async () => {
52
+ const db = getFirestore();
53
+ if (!db) {
54
+ setIsLoading(false);
55
+ return;
56
+ }
57
+
58
+ try {
59
+ setIsLoading(true);
60
+
61
+ // Fetch requests
62
+ const q = query(collection(db, COLLECTION), orderBy("votes", "desc"));
63
+ const snapshot = await getDocs(q);
64
+
65
+ const items: FeatureRequestItem[] = snapshot.docs.map((d) => {
66
+ const data = d.data();
67
+ return {
68
+ id: d.id,
69
+ title: data.title ?? "",
70
+ description: data.description ?? "",
71
+ type: data.type ?? "feature_request",
72
+ status: (data.status ?? "pending") as FeatureRequestStatus,
73
+ votes: data.votes ?? 0,
74
+ commentCount: data.commentCount ?? 0,
75
+ createdBy: data.createdBy ?? "",
76
+ isAnonymous: data.isAnonymous ?? false,
77
+ platform: data.platform ?? "unknown",
78
+ createdAt: data.createdAt?.toDate?.()?.toISOString?.() ?? new Date().toISOString(),
79
+ updatedAt: data.updatedAt?.toDate?.()?.toISOString?.() ?? new Date().toISOString(),
80
+ };
81
+ });
82
+
83
+ setRequests(items);
84
+
85
+ // Fetch user votes
86
+ if (userId) {
87
+ const votes: Record<string, VoteType> = {};
88
+ for (const reqDoc of snapshot.docs) {
89
+ const voteRef = doc(db, COLLECTION, reqDoc.id, "votes", userId);
90
+ const voteSnap = await getDoc(voteRef);
91
+ if (voteSnap.exists()) {
92
+ votes[reqDoc.id] = voteSnap.data().type as VoteType;
93
+ }
94
+ }
95
+ setUserVotes(votes);
96
+ }
97
+ } catch (error) {
98
+ if (__DEV__) console.warn("[useFeatureRequests] Load failed:", error);
99
+ } finally {
100
+ setIsLoading(false);
101
+ }
102
+ }, [userId]);
103
+
104
+ useEffect(() => {
105
+ fetchAll();
106
+ }, [fetchAll]);
107
+
108
+ const vote = useCallback(async (requestId: string, type: VoteType) => {
109
+ if (!userId) return;
110
+
111
+ const db = getFirestore();
112
+ if (!db) return;
113
+
114
+ const previousVote = userVotes[requestId];
115
+ const isUndo = previousVote === type;
116
+
117
+ // Optimistic UI
118
+ setUserVotes((prev) => {
119
+ const next = { ...prev };
120
+ if (isUndo) { delete next[requestId]; } else { next[requestId] = type; }
121
+ return next;
122
+ });
123
+ setRequests((prev) =>
124
+ prev.map((r) => {
125
+ if (r.id !== requestId) return r;
126
+ let delta = 0;
127
+ if (isUndo) delta = type === "up" ? -1 : 1;
128
+ else if (previousVote) delta = type === "up" ? 2 : -2;
129
+ else delta = type === "up" ? 1 : -1;
130
+ return { ...r, votes: r.votes + delta };
131
+ }),
132
+ );
133
+
134
+ try {
135
+ const voteRef = doc(db, COLLECTION, requestId, "votes", userId);
136
+ const requestRef = doc(db, COLLECTION, requestId);
137
+ const existing = await getDoc(voteRef);
138
+
139
+ if (existing.exists()) {
140
+ const prev = existing.data().type as VoteType;
141
+ if (prev === type) {
142
+ await deleteDoc(voteRef);
143
+ await updateDoc(requestRef, { votes: increment(type === "up" ? -1 : 1) });
144
+ } else {
145
+ await setDoc(voteRef, { type, votedAt: serverTimestamp() });
146
+ await updateDoc(requestRef, { votes: increment(type === "up" ? 2 : -2) });
147
+ }
148
+ } else {
149
+ await setDoc(voteRef, { type, votedAt: serverTimestamp() });
150
+ await updateDoc(requestRef, { votes: increment(type === "up" ? 1 : -1) });
151
+ }
152
+ } catch (error) {
153
+ // Rollback
154
+ if (__DEV__) console.warn("[useFeatureRequests] Vote failed:", error);
155
+ setUserVotes((prev) => {
156
+ const next = { ...prev };
157
+ if (previousVote) next[requestId] = previousVote; else delete next[requestId];
158
+ return next;
159
+ });
160
+ fetchAll();
161
+ }
162
+ }, [userId, userVotes, fetchAll]);
163
+
164
+ const submitRequest = useCallback(async (data: { title: string; description: string; type: string; rating?: number }) => {
165
+ const db = getFirestore();
166
+ if (!db) throw new Error("Firestore not available");
167
+ if (!userId) throw new Error("User not authenticated");
168
+
169
+ await addDoc(collection(db, COLLECTION), {
170
+ title: data.title,
171
+ description: data.description,
172
+ type: data.type || "feature_request",
173
+ status: "pending",
174
+ votes: 1,
175
+ commentCount: 0,
176
+ createdBy: userId,
177
+ isAnonymous: user?.isAnonymous ?? false,
178
+ platform: Platform.OS,
179
+ rating: data.rating ?? null,
180
+ createdAt: serverTimestamp(),
181
+ updatedAt: serverTimestamp(),
182
+ });
183
+
184
+ await fetchAll();
185
+ }, [userId, user?.isAnonymous, fetchAll]);
186
+
187
+ return { requests, userVotes, isLoading, vote, submitRequest, reload: fetchAll, userId };
188
+ }
@@ -1,4 +1,4 @@
1
- import React, { useState, useEffect, useCallback } from "react";
1
+ import React, { useState, useCallback } from "react";
2
2
  import {
3
3
  View,
4
4
  StyleSheet,
@@ -15,29 +15,13 @@ import {
15
15
  import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
16
16
  import { ScreenLayout } from "@umituz/react-native-design-system/layouts";
17
17
  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";
18
+ import { useFeatureRequests } from "../../infrastructure/useFeatureRequests";
19
+ import type { FeatureRequestItem, VoteType } from "../../domain/entities/FeatureRequestEntity";
24
20
 
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 }) => {
21
+ export const FeatureRequestScreen: React.FC<any> = ({ config, texts }) => {
35
22
  const tokens = useAppDesignTokens();
36
- const dataProvider = config?.dataProvider;
23
+ const { requests, userVotes, isLoading, vote, submitRequest, userId } = useFeatureRequests();
37
24
 
38
- const [requests, setRequests] = useState<FeatureRequestItem[]>([]);
39
- const [userVotes, setUserVotes] = useState<Record<string, VoteType>>({});
40
- const [isLoading, setIsLoading] = useState(true);
41
25
  const [activeTab, setActiveTab] = useState<'all' | 'my' | 'roadmap'>('all');
42
26
  const [isModalVisible, setIsModalVisible] = useState(false);
43
27
  const [isSubmitting, setIsSubmitting] = useState(false);
@@ -63,101 +47,26 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
63
47
  dismissed: t.status?.dismissed || "Dismissed",
64
48
  };
65
49
 
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
50
  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]);
51
+ await vote(id, type);
52
+ }, [vote]);
140
53
 
141
54
  const handleSubmit = useCallback(async (data: any) => {
142
- if (!dataProvider?.onSubmit) return;
143
-
144
55
  setIsSubmitting(true);
145
56
  try {
146
- await dataProvider.onSubmit({
57
+ await submitRequest({
147
58
  title: data.title || "New Request",
148
59
  description: data.description,
149
60
  type: data.type || "feature_request",
150
61
  rating: data.rating,
151
62
  });
152
63
  setIsModalVisible(false);
153
- // Reload data to get the new request from the database
154
- await loadData();
155
64
  } catch (error) {
156
65
  if (__DEV__) console.warn("[FeatureRequestScreen] Submit failed:", error);
157
66
  } finally {
158
67
  setIsSubmitting(false);
159
68
  }
160
- }, [dataProvider, loadData]);
69
+ }, [submitRequest]);
161
70
 
162
71
  const getStatusColor = (status: string) => {
163
72
  switch (status) {
@@ -173,9 +82,9 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
173
82
  const filteredRequests = (() => {
174
83
  switch (activeTab) {
175
84
  case 'my':
176
- return requests.filter(r => r.createdBy === dataProvider?.userId);
85
+ return requests.filter(r => r.createdBy === userId);
177
86
  case 'roadmap':
178
- return requests.filter(r => r.status === 'planned' || r.status === 'completed' || r.status === 'review');
87
+ return requests.filter(r => ['planned', 'completed', 'review'].includes(r.status));
179
88
  default:
180
89
  return requests;
181
90
  }
@@ -188,21 +97,13 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
188
97
  <View style={[styles.card, { backgroundColor: tokens.colors.surfaceSecondary, borderColor: tokens.colors.borderLight }]}>
189
98
  <View style={styles.voteColumn}>
190
99
  <TouchableOpacity onPress={() => handleVote(item.id, 'up')}>
191
- <AtomicIcon
192
- name="chevron-up"
193
- size="md"
194
- color={voted === 'up' ? "primary" : "textSecondary" as any}
195
- />
100
+ <AtomicIcon name="chevron-up" size="md" color={voted === 'up' ? "primary" : "textSecondary" as any} />
196
101
  </TouchableOpacity>
197
102
  <AtomicText style={[styles.voteCount, { color: voted === 'up' ? tokens.colors.primary : tokens.colors.textPrimary }]}>
198
103
  {item.votes}
199
104
  </AtomicText>
200
105
  <TouchableOpacity onPress={() => handleVote(item.id, 'down')}>
201
- <AtomicIcon
202
- name="chevron-down"
203
- size="md"
204
- color={voted === 'down' ? "primary" : "textSecondary" as any}
205
- />
106
+ <AtomicIcon name="chevron-down" size="md" color={voted === 'down' ? "primary" : "textSecondary" as any} />
206
107
  </TouchableOpacity>
207
108
  </View>
208
109
 
@@ -221,11 +122,9 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
221
122
  </AtomicText>
222
123
 
223
124
  <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>
125
+ <AtomicText style={[styles.platformText, { color: tokens.colors.textTertiary }]}>
126
+ {item.platform.toUpperCase()}
127
+ </AtomicText>
229
128
  <AtomicText style={[styles.commentCount, { color: tokens.colors.textTertiary }]}>
230
129
  {item.commentCount} {t.comment_count?.replace('{{count}}', '') || 'comments'}
231
130
  </AtomicText>
@@ -332,180 +231,36 @@ export const FeatureRequestScreen: React.FC<FeatureRequestScreenProps> = ({ conf
332
231
  };
333
232
 
334
233
  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
- },
234
+ container: { padding: 16 },
235
+ header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16, paddingVertical: 12 },
236
+ headerTitle: { fontSize: 20, fontWeight: '800' },
237
+ addButton: { width: 36, height: 36, borderRadius: 12, alignItems: 'center', justifyContent: 'center' },
238
+ tabsContainer: { flexDirection: 'row', paddingHorizontal: 16, borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.05)' },
239
+ tab: { paddingVertical: 12, paddingHorizontal: 16, borderBottomWidth: 2, borderBottomColor: 'transparent' },
240
+ tabLabel: { fontSize: 14, fontWeight: '600', color: 'rgba(255,255,255,0.5)' },
241
+ banner: { flexDirection: 'row', alignItems: 'center', padding: 16, borderRadius: 16, borderWidth: 1, gap: 12, marginBottom: 20 },
242
+ bannerIconContainer: { position: 'relative' },
243
+ pulseDot: { position: 'absolute', top: 0, right: 0, width: 8, height: 8, borderRadius: 4, backgroundColor: '#10b981' },
244
+ bannerTitle: { fontSize: 14, fontWeight: '700' },
245
+ bannerSub: { fontSize: 12 },
246
+ sectionTitle: { fontSize: 18, fontWeight: '700', marginBottom: 16 },
247
+ listContent: { gap: 12, paddingBottom: 40 },
248
+ card: { flexDirection: 'row', padding: 16, borderRadius: 16, borderWidth: 1, gap: 12 },
249
+ voteColumn: { alignItems: 'center', gap: 4, width: 40 },
250
+ voteCount: { fontSize: 13, fontWeight: '800' },
251
+ cardContent: { flex: 1, gap: 8 },
252
+ cardHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 8 },
253
+ cardTitle: { fontSize: 15, fontWeight: '700', flex: 1 },
254
+ statusBadge: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 8, borderWidth: 1 },
255
+ statusText: { fontSize: 9, fontWeight: '900' },
256
+ cardDescription: { fontSize: 13, lineHeight: 18 },
257
+ cardFooter: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginTop: 4 },
258
+ platformText: { fontSize: 10, fontWeight: '600' },
259
+ commentCount: { fontSize: 11 },
260
+ floatingHint: { position: 'absolute', bottom: 40, right: 16, zIndex: 100 },
261
+ hintBadge: { paddingHorizontal: 12, paddingVertical: 6, borderRadius: 20, shadowColor: '#000', shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.3, shadowRadius: 4, elevation: 8 },
262
+ hintText: { color: '#fff', fontSize: 10, fontWeight: '900', textTransform: 'uppercase' },
263
+ loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingTop: 100 },
264
+ emptyState: { alignItems: 'center', justifyContent: 'center', paddingVertical: 60, gap: 12 },
265
+ emptyText: { fontSize: 14, fontWeight: '500' },
511
266
  });
@@ -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
  /**