@umituz/react-native-ai-generation-content 1.12.35 → 1.12.37
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 +1 -1
- package/src/domains/creations/application/services/CreationsService.ts +1 -0
- package/src/domains/creations/infrastructure/repositories/CreationsFetcher.ts +79 -0
- package/src/domains/creations/infrastructure/repositories/CreationsRepository.ts +30 -186
- package/src/domains/creations/infrastructure/repositories/CreationsWriter.ts +117 -0
- package/src/domains/creations/infrastructure/repositories/FirestorePathResolver.ts +26 -0
- package/src/domains/creations/presentation/components/CreationCard.tsx +1 -1
- package/src/domains/creations/presentation/screens/CreationsGalleryScreen.tsx +8 -6
- package/src/domains/creations/presentation/utils/filterUtils.ts +3 -3
package/package.json
CHANGED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { getDocs, getDoc, query, orderBy } from "firebase/firestore";
|
|
2
|
+
import type { DocumentMapper } from "../../domain/value-objects/CreationsConfig";
|
|
3
|
+
import type { Creation, CreationDocument } from "../../domain/entities/Creation";
|
|
4
|
+
import type { FirestorePathResolver } from "./FirestorePathResolver";
|
|
5
|
+
|
|
6
|
+
declare const __DEV__: boolean;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Handles fetching creations from Firestore
|
|
10
|
+
* Single Responsibility: Read operations
|
|
11
|
+
*/
|
|
12
|
+
export class CreationsFetcher {
|
|
13
|
+
constructor(
|
|
14
|
+
private readonly pathResolver: FirestorePathResolver,
|
|
15
|
+
private readonly documentMapper: DocumentMapper,
|
|
16
|
+
) { }
|
|
17
|
+
|
|
18
|
+
async getAll(userId: string): Promise<Creation[]> {
|
|
19
|
+
if (__DEV__) {
|
|
20
|
+
// eslint-disable-next-line no-console
|
|
21
|
+
console.log("[CreationsRepository] getAll()", { userId });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const userCollection = this.pathResolver.getUserCollection(userId);
|
|
25
|
+
if (!userCollection) return [];
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const q = query(userCollection, orderBy("createdAt", "desc"));
|
|
29
|
+
const snapshot = await getDocs(q);
|
|
30
|
+
|
|
31
|
+
if (__DEV__) {
|
|
32
|
+
// eslint-disable-next-line no-console
|
|
33
|
+
console.log("[CreationsRepository] Fetched:", snapshot.docs.length);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return snapshot.docs.map((docSnap) => {
|
|
37
|
+
const data = docSnap.data() as CreationDocument;
|
|
38
|
+
return this.documentMapper(docSnap.id, data);
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (__DEV__) {
|
|
42
|
+
// eslint-disable-next-line no-console
|
|
43
|
+
console.error("[CreationsRepository] getAll() ERROR", error);
|
|
44
|
+
}
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async getById(userId: string, id: string): Promise<Creation | null> {
|
|
50
|
+
if (__DEV__) {
|
|
51
|
+
// eslint-disable-next-line no-console
|
|
52
|
+
console.log("[CreationsRepository] getById()", { userId, id });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const docRef = this.pathResolver.getDocRef(userId, id);
|
|
56
|
+
if (!docRef) return null;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const docSnap = await getDoc(docRef);
|
|
60
|
+
|
|
61
|
+
if (!docSnap.exists()) {
|
|
62
|
+
if (__DEV__) {
|
|
63
|
+
// eslint-disable-next-line no-console
|
|
64
|
+
console.log("[CreationsRepository] Document not found");
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const data = docSnap.data() as CreationDocument;
|
|
70
|
+
return this.documentMapper(docSnap.id, data);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (__DEV__) {
|
|
73
|
+
// eslint-disable-next-line no-console
|
|
74
|
+
console.error("[CreationsRepository] getById() ERROR", error);
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -1,54 +1,21 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Creations Repository Implementation
|
|
3
|
-
* Extends BaseRepository from @umituz/react-native-firestore
|
|
4
|
-
*
|
|
5
|
-
* Architecture:
|
|
6
|
-
* - Extends BaseRepository for centralized database access
|
|
7
|
-
* - Fully dynamic path structure (configurable per app)
|
|
8
|
-
* - Fully dynamic document mapping (configurable per app)
|
|
9
|
-
* - App-agnostic: Works with any app, no app-specific code
|
|
10
|
-
*
|
|
11
|
-
* This class is designed to be used across hundreds of apps.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
declare const __DEV__: boolean;
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
collection,
|
|
18
|
-
doc,
|
|
19
|
-
getDocs,
|
|
20
|
-
getDoc,
|
|
21
|
-
deleteDoc,
|
|
22
|
-
updateDoc,
|
|
23
|
-
query,
|
|
24
|
-
orderBy,
|
|
25
|
-
setDoc,
|
|
26
|
-
} from "firebase/firestore";
|
|
27
1
|
import { BaseRepository } from "@umituz/react-native-firebase";
|
|
28
2
|
import type { ICreationsRepository } from "../../domain/repositories/ICreationsRepository";
|
|
29
|
-
import type { Creation
|
|
3
|
+
import type { Creation } from "../../domain/entities/Creation";
|
|
30
4
|
import { mapDocumentToCreation } from "../../domain/entities/Creation";
|
|
31
5
|
import type {
|
|
32
6
|
PathBuilder,
|
|
33
7
|
DocumentMapper,
|
|
34
8
|
} from "../../domain/value-objects/CreationsConfig";
|
|
9
|
+
import { FirestorePathResolver } from "./FirestorePathResolver";
|
|
10
|
+
import { CreationsFetcher } from "./CreationsFetcher";
|
|
11
|
+
import { CreationsWriter } from "./CreationsWriter";
|
|
35
12
|
|
|
36
13
|
/**
|
|
37
14
|
* Repository options for dynamic configuration
|
|
38
15
|
* Apps can customize path structure and document mapping
|
|
39
16
|
*/
|
|
40
17
|
export interface RepositoryOptions {
|
|
41
|
-
/**
|
|
42
|
-
* Custom path builder function
|
|
43
|
-
* @example (userId) => ["users", userId, "photos"]
|
|
44
|
-
* @example (userId) => ["creations", userId, "items"]
|
|
45
|
-
*/
|
|
46
18
|
readonly pathBuilder?: PathBuilder;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Custom document mapper function
|
|
50
|
-
* Maps Firestore documents to Creation entity
|
|
51
|
-
*/
|
|
52
19
|
readonly documentMapper?: DocumentMapper;
|
|
53
20
|
}
|
|
54
21
|
|
|
@@ -60,112 +27,48 @@ const createDefaultPathBuilder =
|
|
|
60
27
|
(userId: string) =>
|
|
61
28
|
["users", userId, collectionName];
|
|
62
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Creations Repository Implementation
|
|
32
|
+
* Delegates to specialized classes for different responsibilities
|
|
33
|
+
*
|
|
34
|
+
* Architecture:
|
|
35
|
+
* - Extends BaseRepository for centralized database access
|
|
36
|
+
* - Uses FirestorePathResolver for path resolution
|
|
37
|
+
* - Uses CreationsFetcher for read operations
|
|
38
|
+
* - Uses CreationsWriter for write operations
|
|
39
|
+
* - Fully dynamic and configurable per app
|
|
40
|
+
*/
|
|
63
41
|
export class CreationsRepository
|
|
64
42
|
extends BaseRepository
|
|
65
43
|
implements ICreationsRepository {
|
|
66
|
-
private readonly
|
|
67
|
-
private readonly
|
|
44
|
+
private readonly pathResolver: FirestorePathResolver;
|
|
45
|
+
private readonly fetcher: CreationsFetcher;
|
|
46
|
+
private readonly writer: CreationsWriter;
|
|
68
47
|
|
|
69
48
|
constructor(
|
|
70
49
|
private readonly _collectionName: string,
|
|
71
50
|
options?: RepositoryOptions,
|
|
72
51
|
) {
|
|
73
52
|
super();
|
|
74
|
-
this.pathBuilder =
|
|
75
|
-
options?.pathBuilder ?? createDefaultPathBuilder(_collectionName);
|
|
76
|
-
this.documentMapper = options?.documentMapper ?? mapDocumentToCreation;
|
|
77
|
-
}
|
|
78
53
|
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
if (!db) return null;
|
|
82
|
-
const pathSegments = this.pathBuilder(userId);
|
|
83
|
-
return collection(db, pathSegments[0], ...pathSegments.slice(1));
|
|
84
|
-
}
|
|
54
|
+
const pathBuilder = options?.pathBuilder ?? createDefaultPathBuilder(_collectionName);
|
|
55
|
+
const documentMapper = options?.documentMapper ?? mapDocumentToCreation;
|
|
85
56
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const pathSegments = this.pathBuilder(userId);
|
|
90
|
-
return doc(db, pathSegments[0], ...pathSegments.slice(1), creationId);
|
|
57
|
+
this.pathResolver = new FirestorePathResolver(pathBuilder, this.getDb());
|
|
58
|
+
this.fetcher = new CreationsFetcher(this.pathResolver, documentMapper);
|
|
59
|
+
this.writer = new CreationsWriter(this.pathResolver);
|
|
91
60
|
}
|
|
92
61
|
|
|
93
62
|
async getAll(userId: string): Promise<Creation[]> {
|
|
94
|
-
|
|
95
|
-
// eslint-disable-next-line no-console
|
|
96
|
-
console.log("[CreationsRepository] getAll()", { userId });
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const userCollection = this.getUserCollection(userId);
|
|
100
|
-
if (!userCollection) return [];
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
const q = query(userCollection, orderBy("createdAt", "desc"));
|
|
104
|
-
const snapshot = await getDocs(q);
|
|
105
|
-
|
|
106
|
-
if (__DEV__) {
|
|
107
|
-
// eslint-disable-next-line no-console
|
|
108
|
-
console.log("[CreationsRepository] Fetched:", snapshot.docs.length);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return snapshot.docs.map((docSnap) => {
|
|
112
|
-
const data = docSnap.data() as CreationDocument;
|
|
113
|
-
return this.documentMapper(docSnap.id, data);
|
|
114
|
-
});
|
|
115
|
-
} catch (error) {
|
|
116
|
-
if (__DEV__) {
|
|
117
|
-
// eslint-disable-next-line no-console
|
|
118
|
-
console.error("[CreationsRepository] getAll() ERROR", error);
|
|
119
|
-
}
|
|
120
|
-
return [];
|
|
121
|
-
}
|
|
63
|
+
return this.fetcher.getAll(userId);
|
|
122
64
|
}
|
|
123
65
|
|
|
124
66
|
async getById(userId: string, id: string): Promise<Creation | null> {
|
|
125
|
-
|
|
126
|
-
// eslint-disable-next-line no-console
|
|
127
|
-
console.log("[CreationsRepository] getById()", { userId, id });
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const docRef = this.getDocRef(userId, id);
|
|
131
|
-
if (!docRef) return null;
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
const docSnap = await getDoc(docRef);
|
|
135
|
-
|
|
136
|
-
if (!docSnap.exists()) {
|
|
137
|
-
if (__DEV__) {
|
|
138
|
-
// eslint-disable-next-line no-console
|
|
139
|
-
console.log("[CreationsRepository] Document not found");
|
|
140
|
-
}
|
|
141
|
-
return null;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const data = docSnap.data() as CreationDocument;
|
|
145
|
-
return this.documentMapper(docSnap.id, data);
|
|
146
|
-
} catch (error) {
|
|
147
|
-
if (__DEV__) {
|
|
148
|
-
// eslint-disable-next-line no-console
|
|
149
|
-
console.error("[CreationsRepository] getById() ERROR", error);
|
|
150
|
-
}
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
67
|
+
return this.fetcher.getById(userId, id);
|
|
153
68
|
}
|
|
154
69
|
|
|
155
70
|
async create(userId: string, creation: Creation): Promise<void> {
|
|
156
|
-
|
|
157
|
-
if (!docRef) throw new Error("Firestore not initialized");
|
|
158
|
-
|
|
159
|
-
const data: CreationDocument = {
|
|
160
|
-
type: creation.type,
|
|
161
|
-
prompt: creation.prompt,
|
|
162
|
-
uri: creation.uri, // Use uri
|
|
163
|
-
createdAt: creation.createdAt,
|
|
164
|
-
metadata: creation.metadata || {},
|
|
165
|
-
isShared: creation.isShared || false,
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
await setDoc(docRef, data);
|
|
71
|
+
return this.writer.create(userId, creation);
|
|
169
72
|
}
|
|
170
73
|
|
|
171
74
|
async update(
|
|
@@ -173,54 +76,11 @@ export class CreationsRepository
|
|
|
173
76
|
id: string,
|
|
174
77
|
updates: Partial<Creation>,
|
|
175
78
|
): Promise<boolean> {
|
|
176
|
-
|
|
177
|
-
// eslint-disable-next-line no-console
|
|
178
|
-
console.log("[CreationsRepository] update()", { userId, id, updates });
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const docRef = this.getDocRef(userId, id);
|
|
182
|
-
if (!docRef) return false;
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
const updateData: Record<string, unknown> = {};
|
|
186
|
-
|
|
187
|
-
if (updates.metadata !== undefined) {
|
|
188
|
-
updateData.metadata = updates.metadata;
|
|
189
|
-
}
|
|
190
|
-
if (updates.isShared !== undefined) {
|
|
191
|
-
updateData.isShared = updates.isShared;
|
|
192
|
-
}
|
|
193
|
-
if (updates.uri !== undefined) {
|
|
194
|
-
updateData.uri = updates.uri;
|
|
195
|
-
}
|
|
196
|
-
if (updates.type !== undefined) {
|
|
197
|
-
updateData.type = updates.type;
|
|
198
|
-
}
|
|
199
|
-
if (updates.prompt !== undefined) {
|
|
200
|
-
updateData.prompt = updates.prompt;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
await updateDoc(docRef, updateData);
|
|
204
|
-
return true;
|
|
205
|
-
} catch (error) {
|
|
206
|
-
if (__DEV__) {
|
|
207
|
-
// eslint-disable-next-line no-console
|
|
208
|
-
console.error("[CreationsRepository] update() ERROR", error);
|
|
209
|
-
}
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
79
|
+
return this.writer.update(userId, id, updates);
|
|
212
80
|
}
|
|
213
81
|
|
|
214
82
|
async delete(userId: string, creationId: string): Promise<boolean> {
|
|
215
|
-
|
|
216
|
-
if (!docRef) return false;
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
await deleteDoc(docRef);
|
|
220
|
-
return true;
|
|
221
|
-
} catch {
|
|
222
|
-
return false;
|
|
223
|
-
}
|
|
83
|
+
return this.writer.delete(userId, creationId);
|
|
224
84
|
}
|
|
225
85
|
|
|
226
86
|
async updateShared(
|
|
@@ -228,15 +88,7 @@ export class CreationsRepository
|
|
|
228
88
|
creationId: string,
|
|
229
89
|
isShared: boolean,
|
|
230
90
|
): Promise<boolean> {
|
|
231
|
-
|
|
232
|
-
if (!docRef) return false;
|
|
233
|
-
|
|
234
|
-
try {
|
|
235
|
-
await updateDoc(docRef, { isShared });
|
|
236
|
-
return true;
|
|
237
|
-
} catch {
|
|
238
|
-
return false;
|
|
239
|
-
}
|
|
91
|
+
return this.writer.updateShared(userId, creationId, isShared);
|
|
240
92
|
}
|
|
241
93
|
|
|
242
94
|
async updateFavorite(
|
|
@@ -244,14 +96,6 @@ export class CreationsRepository
|
|
|
244
96
|
creationId: string,
|
|
245
97
|
isFavorite: boolean,
|
|
246
98
|
): Promise<boolean> {
|
|
247
|
-
|
|
248
|
-
if (!docRef) return false;
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
await updateDoc(docRef, { isFavorite });
|
|
252
|
-
return true;
|
|
253
|
-
} catch {
|
|
254
|
-
return false;
|
|
255
|
-
}
|
|
99
|
+
return this.writer.updateFavorite(userId, creationId, isFavorite);
|
|
256
100
|
}
|
|
257
101
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { setDoc, updateDoc, deleteDoc } from "firebase/firestore";
|
|
2
|
+
import type { Creation, CreationDocument } from "../../domain/entities/Creation";
|
|
3
|
+
import type { FirestorePathResolver } from "./FirestorePathResolver";
|
|
4
|
+
|
|
5
|
+
declare const __DEV__: boolean;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Handles write operations for creations
|
|
9
|
+
* Single Responsibility: Write operations
|
|
10
|
+
*/
|
|
11
|
+
export class CreationsWriter {
|
|
12
|
+
constructor(private readonly pathResolver: FirestorePathResolver) { }
|
|
13
|
+
|
|
14
|
+
async create(userId: string, creation: Creation): Promise<void> {
|
|
15
|
+
const docRef = this.pathResolver.getDocRef(userId, creation.id);
|
|
16
|
+
if (!docRef) throw new Error("Firestore not initialized");
|
|
17
|
+
|
|
18
|
+
const data: CreationDocument = {
|
|
19
|
+
type: creation.type,
|
|
20
|
+
prompt: creation.prompt,
|
|
21
|
+
uri: creation.uri,
|
|
22
|
+
createdAt: creation.createdAt,
|
|
23
|
+
metadata: creation.metadata || {},
|
|
24
|
+
isShared: creation.isShared || false,
|
|
25
|
+
isFavorite: creation.isFavorite || false,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
await setDoc(docRef, data);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async update(
|
|
32
|
+
userId: string,
|
|
33
|
+
id: string,
|
|
34
|
+
updates: Partial<Creation>,
|
|
35
|
+
): Promise<boolean> {
|
|
36
|
+
if (__DEV__) {
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.log("[CreationsRepository] update()", { userId, id, updates });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const docRef = this.pathResolver.getDocRef(userId, id);
|
|
42
|
+
if (!docRef) return false;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const updateData: Record<string, unknown> = {};
|
|
46
|
+
|
|
47
|
+
if (updates.metadata !== undefined) {
|
|
48
|
+
updateData.metadata = updates.metadata;
|
|
49
|
+
}
|
|
50
|
+
if (updates.isShared !== undefined) {
|
|
51
|
+
updateData.isShared = updates.isShared;
|
|
52
|
+
}
|
|
53
|
+
if (updates.uri !== undefined) {
|
|
54
|
+
updateData.uri = updates.uri;
|
|
55
|
+
}
|
|
56
|
+
if (updates.type !== undefined) {
|
|
57
|
+
updateData.type = updates.type;
|
|
58
|
+
}
|
|
59
|
+
if (updates.prompt !== undefined) {
|
|
60
|
+
updateData.prompt = updates.prompt;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await updateDoc(docRef, updateData);
|
|
64
|
+
return true;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
if (__DEV__) {
|
|
67
|
+
// eslint-disable-next-line no-console
|
|
68
|
+
console.error("[CreationsRepository] update() ERROR", error);
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async delete(userId: string, creationId: string): Promise<boolean> {
|
|
75
|
+
const docRef = this.pathResolver.getDocRef(userId, creationId);
|
|
76
|
+
if (!docRef) return false;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await deleteDoc(docRef);
|
|
80
|
+
return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async updateShared(
|
|
87
|
+
userId: string,
|
|
88
|
+
creationId: string,
|
|
89
|
+
isShared: boolean,
|
|
90
|
+
): Promise<boolean> {
|
|
91
|
+
const docRef = this.pathResolver.getDocRef(userId, creationId);
|
|
92
|
+
if (!docRef) return false;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
await updateDoc(docRef, { isShared });
|
|
96
|
+
return true;
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async updateFavorite(
|
|
103
|
+
userId: string,
|
|
104
|
+
creationId: string,
|
|
105
|
+
isFavorite: boolean,
|
|
106
|
+
): Promise<boolean> {
|
|
107
|
+
const docRef = this.pathResolver.getDocRef(userId, creationId);
|
|
108
|
+
if (!docRef) return false;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
await updateDoc(docRef, { isFavorite });
|
|
112
|
+
return true;
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Firestore } from "firebase/firestore";
|
|
2
|
+
import { collection, doc } from "firebase/firestore";
|
|
3
|
+
import type { PathBuilder } from "../../domain/value-objects/CreationsConfig";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolves Firestore paths for creations
|
|
7
|
+
* Single Responsibility: Path resolution
|
|
8
|
+
*/
|
|
9
|
+
export class FirestorePathResolver {
|
|
10
|
+
constructor(
|
|
11
|
+
private readonly pathBuilder: PathBuilder,
|
|
12
|
+
private readonly db: Firestore | null,
|
|
13
|
+
) { }
|
|
14
|
+
|
|
15
|
+
getUserCollection(userId: string) {
|
|
16
|
+
if (!this.db) return null;
|
|
17
|
+
const pathSegments = this.pathBuilder(userId);
|
|
18
|
+
return collection(this.db, pathSegments[0], ...pathSegments.slice(1));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
getDocRef(userId: string, creationId: string) {
|
|
22
|
+
if (!this.db) return null;
|
|
23
|
+
const pathSegments = this.pathBuilder(userId);
|
|
24
|
+
return doc(this.db, pathSegments[0], ...pathSegments.slice(1), creationId);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -117,7 +117,7 @@ export function CreationCard({
|
|
|
117
117
|
<View style={styles.content}>
|
|
118
118
|
<View>
|
|
119
119
|
<View style={styles.typeRow}>
|
|
120
|
-
<AtomicIcon name={icon} size="sm" color="primary" />
|
|
120
|
+
{icon && <AtomicIcon name={icon} size="sm" color="primary" />}
|
|
121
121
|
<AtomicText style={styles.typeText}>{label}</AtomicText>
|
|
122
122
|
</View>
|
|
123
123
|
<AtomicText style={styles.dateText}>{formattedDate}</AtomicText>
|
|
@@ -98,12 +98,14 @@ export function CreationsGalleryScreen({
|
|
|
98
98
|
}, []);
|
|
99
99
|
|
|
100
100
|
// Handle favorite toggle
|
|
101
|
-
const handleFavorite = useCallback(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
101
|
+
const handleFavorite = useCallback((creation: Creation, isFavorite: boolean) => {
|
|
102
|
+
void (async () => {
|
|
103
|
+
if (!userId) return;
|
|
104
|
+
const success = await repository.updateFavorite(userId, creation.id, isFavorite);
|
|
105
|
+
if (success) {
|
|
106
|
+
void refetch();
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
107
109
|
}, [userId, repository, refetch]);
|
|
108
110
|
|
|
109
111
|
const styles = useStyles(tokens);
|
|
@@ -14,7 +14,7 @@ export const getFilterCategoriesFromConfig = (
|
|
|
14
14
|
): FilterCategory[] => {
|
|
15
15
|
const categories: FilterCategory[] = [];
|
|
16
16
|
|
|
17
|
-
if (config.types.length > 0) {
|
|
17
|
+
if (config.types && config.types.length > 0) {
|
|
18
18
|
categories.push({
|
|
19
19
|
id: 'type',
|
|
20
20
|
title: t(config.translations.filterTitle),
|
|
@@ -22,12 +22,12 @@ export const getFilterCategoriesFromConfig = (
|
|
|
22
22
|
options: config.types.map(type => ({
|
|
23
23
|
id: type.id,
|
|
24
24
|
label: t(type.labelKey),
|
|
25
|
-
icon: type.icon
|
|
25
|
+
icon: type.icon
|
|
26
26
|
}))
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
if (config.filterCategories) {
|
|
30
|
+
if (config.filterCategories && config.filterCategories.length > 0) {
|
|
31
31
|
categories.push(...config.filterCategories);
|
|
32
32
|
}
|
|
33
33
|
|