@umituz/react-native-settings 5.3.52 → 5.3.53
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.
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* useFeatureRequests Hook
|
|
3
3
|
* Internal hook — fetches, votes, submits feature requests via Firestore
|
|
4
4
|
* Works with both authenticated and anonymous users
|
|
5
|
+
*
|
|
6
|
+
* FIREBASE LAZY LOADING:
|
|
7
|
+
* Firebase is lazy-loaded to avoid hard dependency.
|
|
8
|
+
* If @umituz/react-native-firebase is not installed, features are disabled gracefully.
|
|
5
9
|
*/
|
|
6
10
|
import type { FeatureRequestItem, VoteType } from "../domain/entities/FeatureRequestEntity";
|
|
7
11
|
export interface UseFeatureRequestsResult {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-settings",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.53",
|
|
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",
|
|
@@ -140,6 +140,9 @@
|
|
|
140
140
|
"peerDependenciesMeta": {
|
|
141
141
|
"@umituz/react-native-auth": {
|
|
142
142
|
"optional": true
|
|
143
|
+
},
|
|
144
|
+
"@umituz/react-native-firebase": {
|
|
145
|
+
"optional": true
|
|
143
146
|
}
|
|
144
147
|
},
|
|
145
148
|
"devDependencies": {
|
|
@@ -2,27 +2,16 @@
|
|
|
2
2
|
* useFeatureRequests Hook
|
|
3
3
|
* Internal hook — fetches, votes, submits feature requests via Firestore
|
|
4
4
|
* Works with both authenticated and anonymous users
|
|
5
|
+
*
|
|
6
|
+
* FIREBASE LAZY LOADING:
|
|
7
|
+
* Firebase is lazy-loaded to avoid hard dependency.
|
|
8
|
+
* If @umituz/react-native-firebase is not installed, features are disabled gracefully.
|
|
5
9
|
*/
|
|
6
10
|
|
|
7
11
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
8
12
|
import { Platform } from "react-native";
|
|
9
13
|
import * as Crypto from "expo-crypto";
|
|
10
14
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
|
11
|
-
import {
|
|
12
|
-
collection,
|
|
13
|
-
doc,
|
|
14
|
-
getDocs,
|
|
15
|
-
getDoc,
|
|
16
|
-
setDoc,
|
|
17
|
-
addDoc,
|
|
18
|
-
updateDoc,
|
|
19
|
-
deleteDoc,
|
|
20
|
-
increment,
|
|
21
|
-
orderBy,
|
|
22
|
-
query,
|
|
23
|
-
serverTimestamp,
|
|
24
|
-
} from "firebase/firestore";
|
|
25
|
-
import { getFirestore } from "@umituz/react-native-firebase";
|
|
26
15
|
import type {
|
|
27
16
|
FeatureRequestItem,
|
|
28
17
|
FeatureRequestStatus,
|
|
@@ -32,6 +21,59 @@ import type {
|
|
|
32
21
|
const COLLECTION = "feature_requests";
|
|
33
22
|
const ANONYMOUS_USER_ID_KEY = "@anonymous_user_id";
|
|
34
23
|
|
|
24
|
+
// Lazy-loaded Firebase modules
|
|
25
|
+
let firebaseModules: {
|
|
26
|
+
getFirestore: () => any;
|
|
27
|
+
collection: any;
|
|
28
|
+
doc: any;
|
|
29
|
+
getDocs: any;
|
|
30
|
+
getDoc: any;
|
|
31
|
+
setDoc: any;
|
|
32
|
+
addDoc: any;
|
|
33
|
+
updateDoc: any;
|
|
34
|
+
deleteDoc: any;
|
|
35
|
+
increment: any;
|
|
36
|
+
orderBy: any;
|
|
37
|
+
query: any;
|
|
38
|
+
serverTimestamp: any;
|
|
39
|
+
} | null = null;
|
|
40
|
+
|
|
41
|
+
function loadFirebase(): typeof firebaseModules {
|
|
42
|
+
if (firebaseModules !== null) return firebaseModules;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// Lazy load Firebase only when needed
|
|
46
|
+
const firebaseModule = require("@umituz/react-native-firebase");
|
|
47
|
+
const firestoreModule = require("firebase/firestore");
|
|
48
|
+
|
|
49
|
+
firebaseModules = {
|
|
50
|
+
getFirestore: firebaseModule.getFirestore,
|
|
51
|
+
collection: firestoreModule.collection,
|
|
52
|
+
doc: firestoreModule.doc,
|
|
53
|
+
getDocs: firestoreModule.getDocs,
|
|
54
|
+
getDoc: firestoreModule.getDoc,
|
|
55
|
+
setDoc: firestoreModule.setDoc,
|
|
56
|
+
addDoc: firestoreModule.addDoc,
|
|
57
|
+
updateDoc: firestoreModule.updateDoc,
|
|
58
|
+
deleteDoc: firestoreModule.deleteDoc,
|
|
59
|
+
increment: firestoreModule.increment,
|
|
60
|
+
orderBy: firestoreModule.orderBy,
|
|
61
|
+
query: firestoreModule.query,
|
|
62
|
+
serverTimestamp: firestoreModule.serverTimestamp,
|
|
63
|
+
};
|
|
64
|
+
return firebaseModules;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
// Firebase not installed - return null to disable features
|
|
67
|
+
if (__DEV__) {
|
|
68
|
+
console.warn(
|
|
69
|
+
"[useFeatureRequests] @umituz/react-native-firebase not installed. Feature requests disabled.",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
firebaseModules = null;
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
35
77
|
/**
|
|
36
78
|
* Get or create a persistent anonymous user ID
|
|
37
79
|
*/
|
|
@@ -88,7 +130,13 @@ export function useFeatureRequests(): UseFeatureRequestsResult {
|
|
|
88
130
|
const votingInProgress = useRef(new Set<string>());
|
|
89
131
|
|
|
90
132
|
const fetchAll = useCallback(async () => {
|
|
91
|
-
const
|
|
133
|
+
const fb = loadFirebase();
|
|
134
|
+
if (!fb) {
|
|
135
|
+
setIsLoading(false);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const db = fb.getFirestore();
|
|
92
140
|
if (!db) {
|
|
93
141
|
setIsLoading(false);
|
|
94
142
|
return;
|
|
@@ -97,8 +145,8 @@ export function useFeatureRequests(): UseFeatureRequestsResult {
|
|
|
97
145
|
try {
|
|
98
146
|
setIsLoading(true);
|
|
99
147
|
|
|
100
|
-
const q = query(collection(db, COLLECTION), orderBy("votes", "desc"));
|
|
101
|
-
const snapshot = await getDocs(q);
|
|
148
|
+
const q = fb.query(fb.collection(db, COLLECTION), fb.orderBy("votes", "desc"));
|
|
149
|
+
const snapshot = await fb.getDocs(q);
|
|
102
150
|
|
|
103
151
|
const items: FeatureRequestItem[] = snapshot.docs.map((d) => {
|
|
104
152
|
const data = d.data();
|
|
@@ -123,8 +171,8 @@ export function useFeatureRequests(): UseFeatureRequestsResult {
|
|
|
123
171
|
// Fetch user votes in parallel (not N+1 sequential)
|
|
124
172
|
if (userId && snapshot.docs.length > 0) {
|
|
125
173
|
const votePromises = snapshot.docs.map(async (reqDoc) => {
|
|
126
|
-
const voteRef = doc(db, COLLECTION, reqDoc.id, "votes", userId);
|
|
127
|
-
const voteSnap = await getDoc(voteRef);
|
|
174
|
+
const voteRef = fb.doc(db, COLLECTION, reqDoc.id, "votes", userId);
|
|
175
|
+
const voteSnap = await fb.getDoc(voteRef);
|
|
128
176
|
if (voteSnap.exists()) {
|
|
129
177
|
return [reqDoc.id, voteSnap.data().type as VoteType] as const;
|
|
130
178
|
}
|
|
@@ -152,13 +200,14 @@ export function useFeatureRequests(): UseFeatureRequestsResult {
|
|
|
152
200
|
}, [fetchAll]);
|
|
153
201
|
|
|
154
202
|
const vote = useCallback(async (requestId: string, type: VoteType) => {
|
|
155
|
-
|
|
203
|
+
const fb = loadFirebase();
|
|
204
|
+
if (!fb || !userId) return;
|
|
156
205
|
|
|
157
206
|
// Prevent rapid double-tap on the same request
|
|
158
207
|
if (votingInProgress.current.has(requestId)) return;
|
|
159
208
|
votingInProgress.current.add(requestId);
|
|
160
209
|
|
|
161
|
-
const db = getFirestore();
|
|
210
|
+
const db = fb.getFirestore();
|
|
162
211
|
if (!db) {
|
|
163
212
|
votingInProgress.current.delete(requestId);
|
|
164
213
|
return;
|
|
@@ -186,22 +235,22 @@ export function useFeatureRequests(): UseFeatureRequestsResult {
|
|
|
186
235
|
);
|
|
187
236
|
|
|
188
237
|
try {
|
|
189
|
-
const voteRef = doc(db, COLLECTION, requestId, "votes", userId);
|
|
190
|
-
const requestRef = doc(db, COLLECTION, requestId);
|
|
191
|
-
const existing = await getDoc(voteRef);
|
|
238
|
+
const voteRef = fb.doc(db, COLLECTION, requestId, "votes", userId);
|
|
239
|
+
const requestRef = fb.doc(db, COLLECTION, requestId);
|
|
240
|
+
const existing = await fb.getDoc(voteRef);
|
|
192
241
|
|
|
193
242
|
if (existing.exists()) {
|
|
194
243
|
const prev = existing.data().type as VoteType;
|
|
195
244
|
if (prev === type) {
|
|
196
|
-
await deleteDoc(voteRef);
|
|
197
|
-
await updateDoc(requestRef, { votes: increment(type === "up" ? -1 : 1) });
|
|
245
|
+
await fb.deleteDoc(voteRef);
|
|
246
|
+
await fb.updateDoc(requestRef, { votes: fb.increment(type === "up" ? -1 : 1) });
|
|
198
247
|
} else {
|
|
199
|
-
await setDoc(voteRef, { type, votedAt: serverTimestamp() });
|
|
200
|
-
await updateDoc(requestRef, { votes: increment(type === "up" ? 2 : -2) });
|
|
248
|
+
await fb.setDoc(voteRef, { type, votedAt: fb.serverTimestamp() });
|
|
249
|
+
await fb.updateDoc(requestRef, { votes: fb.increment(type === "up" ? 2 : -2) });
|
|
201
250
|
}
|
|
202
251
|
} else {
|
|
203
|
-
await setDoc(voteRef, { type, votedAt: serverTimestamp() });
|
|
204
|
-
await updateDoc(requestRef, { votes: increment(type === "up" ? 1 : -1) });
|
|
252
|
+
await fb.setDoc(voteRef, { type, votedAt: fb.serverTimestamp() });
|
|
253
|
+
await fb.updateDoc(requestRef, { votes: fb.increment(type === "up" ? 1 : -1) });
|
|
205
254
|
}
|
|
206
255
|
} catch (error) {
|
|
207
256
|
if (__DEV__) console.warn("[useFeatureRequests] Vote failed:", error);
|
|
@@ -218,12 +267,14 @@ export function useFeatureRequests(): UseFeatureRequestsResult {
|
|
|
218
267
|
}, [userId, fetchAll]);
|
|
219
268
|
|
|
220
269
|
const submitRequest = useCallback(async (data: { title: string; description: string; type: string; rating?: number }) => {
|
|
221
|
-
const
|
|
270
|
+
const fb = loadFirebase();
|
|
271
|
+
if (!fb) throw new Error("Firestore not available");
|
|
272
|
+
const db = fb.getFirestore();
|
|
222
273
|
if (!db) throw new Error("Firestore not available");
|
|
223
274
|
if (!userId) throw new Error("User ID not available");
|
|
224
275
|
|
|
225
276
|
// Create the feature request
|
|
226
|
-
const docRef = await addDoc(collection(db, COLLECTION), {
|
|
277
|
+
const docRef = await fb.addDoc(fb.collection(db, COLLECTION), {
|
|
227
278
|
title: data.title,
|
|
228
279
|
description: data.description,
|
|
229
280
|
type: data.type || "feature_request",
|
|
@@ -234,14 +285,14 @@ export function useFeatureRequests(): UseFeatureRequestsResult {
|
|
|
234
285
|
isAnonymous: true,
|
|
235
286
|
platform: Platform.OS,
|
|
236
287
|
rating: data.rating ?? null,
|
|
237
|
-
createdAt: serverTimestamp(),
|
|
238
|
-
updatedAt: serverTimestamp(),
|
|
288
|
+
createdAt: fb.serverTimestamp(),
|
|
289
|
+
updatedAt: fb.serverTimestamp(),
|
|
239
290
|
});
|
|
240
291
|
|
|
241
292
|
// Create the creator's upvote doc so votes count matches reality
|
|
242
|
-
await setDoc(doc(db, COLLECTION, docRef.id, "votes", userId), {
|
|
293
|
+
await fb.setDoc(fb.doc(db, COLLECTION, docRef.id, "votes", userId), {
|
|
243
294
|
type: "up" as VoteType,
|
|
244
|
-
votedAt: serverTimestamp(),
|
|
295
|
+
votedAt: fb.serverTimestamp(),
|
|
245
296
|
});
|
|
246
297
|
|
|
247
298
|
await fetchAll();
|