@umituz/web-firebase 1.0.4 → 2.0.1
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/README.md +555 -0
- package/dist/application/index.d.mts +273 -0
- package/dist/application/index.d.ts +273 -0
- package/dist/application/index.js +490 -0
- package/dist/application/index.mjs +19 -0
- package/dist/chunk-34DL2QWQ.mjs +87 -0
- package/dist/chunk-4FP2ELQ5.mjs +96 -0
- package/dist/chunk-7TX3OU3O.mjs +721 -0
- package/dist/chunk-I6WGBPFB.mjs +439 -0
- package/dist/chunk-RZ4QR6TB.mjs +96 -0
- package/dist/chunk-U2XI4MGO.mjs +397 -0
- package/dist/domain/index.d.mts +325 -0
- package/dist/domain/index.d.ts +325 -0
- package/dist/domain/index.js +662 -0
- package/dist/domain/index.mjs +36 -0
- package/dist/file.repository.interface-v5vHgVsZ.d.mts +241 -0
- package/dist/file.repository.interface-v5vHgVsZ.d.ts +241 -0
- package/dist/firebase.entity-xvfEPjXZ.d.mts +15 -0
- package/dist/firebase.entity-xvfEPjXZ.d.ts +15 -0
- package/dist/index.d.mts +14 -96
- package/dist/index.d.ts +14 -96
- package/dist/index.js +1717 -78
- package/dist/index.mjs +88 -175
- package/dist/infrastructure/index.d.mts +170 -0
- package/dist/infrastructure/index.d.ts +170 -0
- package/dist/infrastructure/index.js +856 -0
- package/dist/infrastructure/index.mjs +46 -0
- package/dist/presentation/index.d.mts +25 -0
- package/dist/presentation/index.d.ts +25 -0
- package/dist/presentation/index.js +105 -0
- package/dist/presentation/index.mjs +6 -0
- package/dist/user.repository.interface-DS74TsJ5.d.mts +298 -0
- package/dist/user.repository.interface-DS74TsJ5.d.ts +298 -0
- package/package.json +39 -12
- package/src/application/dto/auth.dto.ts +69 -0
- package/src/application/dto/index.ts +7 -0
- package/src/application/dto/user.dto.ts +64 -0
- package/src/application/index.ts +7 -0
- package/src/application/use-cases/auth/reset-password.use-case.ts +66 -0
- package/src/application/use-cases/auth/sign-in-with-google.use-case.ts +86 -0
- package/src/application/use-cases/auth/sign-in.use-case.ts +77 -0
- package/src/application/use-cases/auth/sign-out.use-case.ts +22 -0
- package/src/application/use-cases/auth/sign-up.use-case.ts +99 -0
- package/src/application/use-cases/index.ts +12 -0
- package/src/application/use-cases/user/delete-account.use-case.ts +77 -0
- package/src/application/use-cases/user/update-profile.use-case.ts +98 -0
- package/src/domain/entities/file.entity.ts +151 -0
- package/src/domain/entities/firebase.entity.ts +13 -0
- package/src/domain/entities/timestamp.entity.ts +116 -0
- package/src/domain/entities/user.entity.ts +193 -0
- package/src/domain/errors/auth.errors.ts +115 -0
- package/src/domain/errors/repository.errors.ts +121 -0
- package/src/domain/index.ts +30 -0
- package/src/domain/interfaces/auth.repository.interface.ts +83 -0
- package/src/domain/interfaces/file.repository.interface.ts +143 -0
- package/src/domain/interfaces/repository.interface.ts +11 -0
- package/src/domain/interfaces/user.repository.interface.ts +75 -0
- package/src/domain/value-objects/email.vo.ts +105 -0
- package/src/domain/value-objects/file-path.vo.ts +184 -0
- package/src/domain/value-objects/user-id.vo.ts +87 -0
- package/src/index.ts +23 -0
- package/src/infrastructure/firebase/auth.adapter.ts +220 -0
- package/src/infrastructure/firebase/client.ts +141 -0
- package/src/infrastructure/firebase/firestore.adapter.ts +190 -0
- package/src/infrastructure/firebase/storage.adapter.ts +323 -0
- package/src/infrastructure/index.ts +13 -0
- package/src/infrastructure/services/firebase.service.ts +30 -0
- package/src/infrastructure/services/firestore.repository.ts +66 -0
- package/src/infrastructure/utils/storage.util.ts +46 -0
- package/src/presentation/hooks/useAuth.ts +153 -0
- package/src/presentation/hooks/useFirebaseAuth.ts +122 -0
- package/src/presentation/hooks/useFirestore.ts +125 -0
- package/src/presentation/hooks/useStorage.ts +141 -0
- package/src/presentation/index.ts +6 -0
- package/src/presentation/providers/FirebaseProvider.tsx +40 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collection,
|
|
3
|
+
doc,
|
|
4
|
+
getDoc,
|
|
5
|
+
getDocs,
|
|
6
|
+
setDoc,
|
|
7
|
+
updateDoc,
|
|
8
|
+
deleteDoc,
|
|
9
|
+
query,
|
|
10
|
+
type QueryConstraint,
|
|
11
|
+
type DocumentData,
|
|
12
|
+
type Firestore,
|
|
13
|
+
type CollectionReference,
|
|
14
|
+
} from 'firebase/firestore';
|
|
15
|
+
import type { IBaseRepository } from '../../domain/interfaces/repository.interface';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Firestore Repository Implementation
|
|
19
|
+
* @description Generic CRUD operations for Firestore collections
|
|
20
|
+
*/
|
|
21
|
+
export class FirestoreRepository<T extends DocumentData> implements IBaseRepository<T> {
|
|
22
|
+
constructor(
|
|
23
|
+
protected db: Firestore,
|
|
24
|
+
protected collectionName: string,
|
|
25
|
+
) {}
|
|
26
|
+
|
|
27
|
+
protected getCollection(parentPath?: string): CollectionReference<T> {
|
|
28
|
+
const fullPath = parentPath ? `${parentPath}/${this.collectionName}` : this.collectionName;
|
|
29
|
+
return collection(this.db, fullPath) as CollectionReference<T>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
protected getDocRef(id: string, parentPath?: string) {
|
|
33
|
+
const fullPath = parentPath ? `${parentPath}/${this.collectionName}` : this.collectionName;
|
|
34
|
+
return doc(this.db, fullPath, id);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async getById(id: string, parentPath?: string): Promise<T | null> {
|
|
38
|
+
const snap = await getDoc(this.getDocRef(id, parentPath));
|
|
39
|
+
return snap.exists() ? ({ id: snap.id, ...snap.data() } as unknown as T) : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async getAll(constraints: QueryConstraint[] = [], parentPath?: string): Promise<T[]> {
|
|
43
|
+
const q = query(this.getCollection(parentPath), ...constraints);
|
|
44
|
+
const snap = await getDocs(q);
|
|
45
|
+
return snap.docs.map((d) => ({ id: d.id, ...d.data() } as unknown as T));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async create(id: string, data: Omit<T, 'id'>, parentPath?: string): Promise<void> {
|
|
49
|
+
await setDoc(this.getDocRef(id, parentPath), {
|
|
50
|
+
...data,
|
|
51
|
+
createdAt: new Date().toISOString(),
|
|
52
|
+
updatedAt: new Date().toISOString(),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async update(id: string, data: Partial<T>, parentPath?: string): Promise<void> {
|
|
57
|
+
await updateDoc(this.getDocRef(id, parentPath), {
|
|
58
|
+
...(data as DocumentData),
|
|
59
|
+
updatedAt: new Date().toISOString(),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async delete(id: string, parentPath?: string): Promise<void> {
|
|
64
|
+
await deleteDoc(this.getDocRef(id, parentPath));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ref,
|
|
3
|
+
uploadBytes,
|
|
4
|
+
uploadString,
|
|
5
|
+
getDownloadURL,
|
|
6
|
+
deleteObject,
|
|
7
|
+
type FirebaseStorage,
|
|
8
|
+
} from 'firebase/storage';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Firebase Storage Utils
|
|
12
|
+
* @description Upload and delete helpers for Firebase Storage
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface StorageUploadResult {
|
|
16
|
+
url: string;
|
|
17
|
+
path: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function uploadFile(
|
|
21
|
+
storage: FirebaseStorage,
|
|
22
|
+
path: string,
|
|
23
|
+
file: File | Blob,
|
|
24
|
+
): Promise<StorageUploadResult> {
|
|
25
|
+
const storageRef = ref(storage, path);
|
|
26
|
+
await uploadBytes(storageRef, file);
|
|
27
|
+
const url = await getDownloadURL(storageRef);
|
|
28
|
+
return { url, path };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function uploadBase64(
|
|
32
|
+
storage: FirebaseStorage,
|
|
33
|
+
path: string,
|
|
34
|
+
base64: string,
|
|
35
|
+
mimeType = 'image/jpeg',
|
|
36
|
+
): Promise<StorageUploadResult> {
|
|
37
|
+
const storageRef = ref(storage, path);
|
|
38
|
+
const dataUrl = base64.startsWith('data:') ? base64 : `data:${mimeType};base64,${base64}`;
|
|
39
|
+
await uploadString(storageRef, dataUrl, 'data_url');
|
|
40
|
+
const url = await getDownloadURL(storageRef);
|
|
41
|
+
return { url, path };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function deleteFile(storage: FirebaseStorage, path: string): Promise<void> {
|
|
45
|
+
await deleteObject(ref(storage, path));
|
|
46
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Hook
|
|
3
|
+
* @description React hook for authentication operations
|
|
4
|
+
* Migrated from: /Users/umituz/Desktop/github/umituz/apps/web/app-growth-factory/src/domains/firebase/hooks/useAuth.ts
|
|
5
|
+
* Refactored to use use cases
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useEffect, useState, useCallback } from 'react'
|
|
9
|
+
import { User as FirebaseUser } from 'firebase/auth'
|
|
10
|
+
import type { User } from '../../domain/entities/user.entity'
|
|
11
|
+
import type { IAuthRepository } from '../../domain/interfaces/auth.repository.interface'
|
|
12
|
+
import type { IUserRepository } from '../../domain/interfaces/user.repository.interface'
|
|
13
|
+
import type { SignInDTO, SignUpDTO } from '../../application/dto/auth.dto'
|
|
14
|
+
|
|
15
|
+
export interface UseAuthOptions {
|
|
16
|
+
authRepository: IAuthRepository
|
|
17
|
+
userRepository: IUserRepository
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function useAuth({ authRepository, userRepository }: UseAuthOptions) {
|
|
21
|
+
const [firebaseUser, setFirebaseUser] = useState<FirebaseUser | null>(null)
|
|
22
|
+
const [user, setUser] = useState<User | null>(null)
|
|
23
|
+
const [loading, setLoading] = useState(true)
|
|
24
|
+
const [error, setError] = useState<Error | null>(null)
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const unsubscribe = authRepository.onAuthStateChanged(async (fbUser) => {
|
|
28
|
+
setFirebaseUser(fbUser)
|
|
29
|
+
|
|
30
|
+
if (fbUser) {
|
|
31
|
+
try {
|
|
32
|
+
const userData = await userRepository.getUser(fbUser.uid)
|
|
33
|
+
setUser(userData)
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error('Error fetching user data:', err)
|
|
36
|
+
setError(err as Error)
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
setUser(null)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setLoading(false)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return () => unsubscribe()
|
|
46
|
+
}, [authRepository, userRepository])
|
|
47
|
+
|
|
48
|
+
const signIn = useCallback(async (dto: SignInDTO) => {
|
|
49
|
+
setError(null)
|
|
50
|
+
try {
|
|
51
|
+
return await authRepository.signIn(dto.email, dto.password)
|
|
52
|
+
} catch (err) {
|
|
53
|
+
setError(err as Error)
|
|
54
|
+
throw err
|
|
55
|
+
}
|
|
56
|
+
}, [authRepository])
|
|
57
|
+
|
|
58
|
+
const signUp = useCallback(async (dto: SignUpDTO) => {
|
|
59
|
+
setError(null)
|
|
60
|
+
try {
|
|
61
|
+
return await authRepository.signUp(dto.email, dto.password, dto.displayName)
|
|
62
|
+
} catch (err) {
|
|
63
|
+
setError(err as Error)
|
|
64
|
+
throw err
|
|
65
|
+
}
|
|
66
|
+
}, [authRepository])
|
|
67
|
+
|
|
68
|
+
const signInWithGoogle = useCallback(async () => {
|
|
69
|
+
setError(null)
|
|
70
|
+
try {
|
|
71
|
+
return await authRepository.signInWithGoogle()
|
|
72
|
+
} catch (err) {
|
|
73
|
+
setError(err as Error)
|
|
74
|
+
throw err
|
|
75
|
+
}
|
|
76
|
+
}, [authRepository])
|
|
77
|
+
|
|
78
|
+
const signOut = useCallback(async () => {
|
|
79
|
+
setError(null)
|
|
80
|
+
try {
|
|
81
|
+
await authRepository.signOut()
|
|
82
|
+
setUser(null)
|
|
83
|
+
setFirebaseUser(null)
|
|
84
|
+
} catch (err) {
|
|
85
|
+
setError(err as Error)
|
|
86
|
+
throw err
|
|
87
|
+
}
|
|
88
|
+
}, [authRepository])
|
|
89
|
+
|
|
90
|
+
const sendPasswordReset = useCallback(async (email: string) => {
|
|
91
|
+
setError(null)
|
|
92
|
+
try {
|
|
93
|
+
await authRepository.sendPasswordReset(email)
|
|
94
|
+
} catch (err) {
|
|
95
|
+
setError(err as Error)
|
|
96
|
+
throw err
|
|
97
|
+
}
|
|
98
|
+
}, [authRepository])
|
|
99
|
+
|
|
100
|
+
const resendEmailVerification = useCallback(async () => {
|
|
101
|
+
setError(null)
|
|
102
|
+
try {
|
|
103
|
+
await authRepository.resendEmailVerification()
|
|
104
|
+
} catch (err) {
|
|
105
|
+
setError(err as Error)
|
|
106
|
+
throw err
|
|
107
|
+
}
|
|
108
|
+
}, [authRepository])
|
|
109
|
+
|
|
110
|
+
const updateProfile = useCallback(async (updates: { displayName?: string; photoURL?: string }) => {
|
|
111
|
+
setError(null)
|
|
112
|
+
try {
|
|
113
|
+
await authRepository.updateProfile(updates)
|
|
114
|
+
if (firebaseUser) {
|
|
115
|
+
const userData = await userRepository.getUser(firebaseUser.uid)
|
|
116
|
+
setUser(userData)
|
|
117
|
+
}
|
|
118
|
+
} catch (err) {
|
|
119
|
+
setError(err as Error)
|
|
120
|
+
throw err
|
|
121
|
+
}
|
|
122
|
+
}, [authRepository, userRepository, firebaseUser])
|
|
123
|
+
|
|
124
|
+
const refreshUser = useCallback(async () => {
|
|
125
|
+
setError(null)
|
|
126
|
+
try {
|
|
127
|
+
if (firebaseUser) {
|
|
128
|
+
const userData = await userRepository.getUser(firebaseUser.uid)
|
|
129
|
+
setUser(userData)
|
|
130
|
+
}
|
|
131
|
+
} catch (err) {
|
|
132
|
+
setError(err as Error)
|
|
133
|
+
throw err
|
|
134
|
+
}
|
|
135
|
+
}, [userRepository, firebaseUser])
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
firebaseUser,
|
|
139
|
+
user,
|
|
140
|
+
loading,
|
|
141
|
+
error,
|
|
142
|
+
isAuthenticated: !!firebaseUser,
|
|
143
|
+
isEmailVerified: firebaseUser?.emailVerified || false,
|
|
144
|
+
signIn,
|
|
145
|
+
signUp,
|
|
146
|
+
signInWithGoogle,
|
|
147
|
+
signOut,
|
|
148
|
+
sendPasswordReset,
|
|
149
|
+
resendEmailVerification,
|
|
150
|
+
updateProfile,
|
|
151
|
+
refreshUser,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
type Auth,
|
|
4
|
+
type UserCredential,
|
|
5
|
+
type User,
|
|
6
|
+
onAuthStateChanged,
|
|
7
|
+
signInWithEmailAndPassword,
|
|
8
|
+
createUserWithEmailAndPassword,
|
|
9
|
+
signOut as firebaseSignOut,
|
|
10
|
+
updateProfile,
|
|
11
|
+
updatePassword,
|
|
12
|
+
sendPasswordResetEmail,
|
|
13
|
+
} from 'firebase/auth';
|
|
14
|
+
import type { FirebaseUser } from '../../domain/entities/firebase.entity';
|
|
15
|
+
|
|
16
|
+
function mapUser(u: User): FirebaseUser {
|
|
17
|
+
return {
|
|
18
|
+
uid: u.uid,
|
|
19
|
+
email: u.email,
|
|
20
|
+
displayName: u.displayName,
|
|
21
|
+
photoURL: u.photoURL,
|
|
22
|
+
emailVerified: u.emailVerified,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* useFirebaseAuth Hook
|
|
28
|
+
* @description Hook to manage Firebase authentication state and operations
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
export interface UseFirebaseAuthOptions {
|
|
32
|
+
onUserChange?: (user: FirebaseUser | null) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface UseFirebaseAuthReturn {
|
|
36
|
+
user: FirebaseUser | null;
|
|
37
|
+
loading: boolean;
|
|
38
|
+
isAuthenticated: boolean;
|
|
39
|
+
signIn(email: string, password: string): Promise<UserCredential>;
|
|
40
|
+
signUp(email: string, password: string, name?: string): Promise<UserCredential>;
|
|
41
|
+
signOut(): Promise<void>;
|
|
42
|
+
updateUserProfile(name: string, photoURL?: string): Promise<void>;
|
|
43
|
+
updateUserPassword(newPassword: string): Promise<void>;
|
|
44
|
+
resetPassword(email: string): Promise<void>;
|
|
45
|
+
getIdToken(): Promise<string>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function useFirebaseAuth(
|
|
49
|
+
auth: Auth,
|
|
50
|
+
options?: UseFirebaseAuthOptions,
|
|
51
|
+
): UseFirebaseAuthReturn {
|
|
52
|
+
const [user, setUser] = useState<FirebaseUser | null>(null);
|
|
53
|
+
const [loading, setLoading] = useState(true);
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const unsub = onAuthStateChanged(auth, (firebaseUser) => {
|
|
57
|
+
const mapped = firebaseUser ? mapUser(firebaseUser) : null;
|
|
58
|
+
setUser(mapped);
|
|
59
|
+
setLoading(false);
|
|
60
|
+
options?.onUserChange?.(mapped);
|
|
61
|
+
});
|
|
62
|
+
return unsub;
|
|
63
|
+
}, [auth, options]);
|
|
64
|
+
|
|
65
|
+
const signIn = useCallback(
|
|
66
|
+
(email: string, password: string) => signInWithEmailAndPassword(auth, email, password),
|
|
67
|
+
[auth],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const signUp = useCallback(
|
|
71
|
+
async (email: string, password: string, name?: string) => {
|
|
72
|
+
const cred = await createUserWithEmailAndPassword(auth, email, password);
|
|
73
|
+
if (name && cred.user) await updateProfile(cred.user, { displayName: name });
|
|
74
|
+
return cred;
|
|
75
|
+
},
|
|
76
|
+
[auth],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const signOut = useCallback(() => firebaseSignOut(auth), [auth]);
|
|
80
|
+
|
|
81
|
+
const updateUserProfile = useCallback(
|
|
82
|
+
async (name: string, photoURL?: string) => {
|
|
83
|
+
if (!auth.currentUser) throw new Error('No authenticated user');
|
|
84
|
+
await updateProfile(auth.currentUser, {
|
|
85
|
+
displayName: name,
|
|
86
|
+
...(photoURL !== undefined && { photoURL }),
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
[auth],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const updateUserPassword = useCallback(
|
|
93
|
+
async (newPassword: string) => {
|
|
94
|
+
if (!auth.currentUser) throw new Error('No authenticated user');
|
|
95
|
+
await updatePassword(auth.currentUser, newPassword);
|
|
96
|
+
},
|
|
97
|
+
[auth],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const resetPassword = useCallback(
|
|
101
|
+
(email: string) => sendPasswordResetEmail(auth, email),
|
|
102
|
+
[auth],
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const getIdToken = useCallback(async () => {
|
|
106
|
+
if (!auth.currentUser) throw new Error('No authenticated user');
|
|
107
|
+
return auth.currentUser.getIdToken();
|
|
108
|
+
}, [auth]);
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
user,
|
|
112
|
+
loading,
|
|
113
|
+
isAuthenticated: !!user,
|
|
114
|
+
signIn,
|
|
115
|
+
signUp,
|
|
116
|
+
signOut,
|
|
117
|
+
updateUserProfile,
|
|
118
|
+
updateUserPassword,
|
|
119
|
+
resetPassword,
|
|
120
|
+
getIdToken,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firestore Hook
|
|
3
|
+
* @description React hook for Firestore real-time data
|
|
4
|
+
* Migrated from: /Users/umituz/Desktop/github/umituz/apps/web/app-growth-factory/src/domains/firebase/hooks/useFirestore.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useEffect, useState } from 'react'
|
|
8
|
+
import { doc, onSnapshot, query, collection, type QuerySnapshot, type DocumentData } from 'firebase/firestore'
|
|
9
|
+
import type { Firestore } from 'firebase/firestore'
|
|
10
|
+
|
|
11
|
+
export function useUser(db: Firestore, userId: string) {
|
|
12
|
+
const [user, setUser] = useState<any>(null)
|
|
13
|
+
const [loading, setLoading] = useState(true)
|
|
14
|
+
const [error, setError] = useState<Error | null>(null)
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!userId) {
|
|
18
|
+
setUser(null)
|
|
19
|
+
setLoading(false)
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const unsubscribe = onSnapshot(doc(db, 'users', userId), (docSnap) => {
|
|
24
|
+
if (docSnap.exists()) {
|
|
25
|
+
setUser(docSnap.data())
|
|
26
|
+
} else {
|
|
27
|
+
setUser(null)
|
|
28
|
+
}
|
|
29
|
+
setLoading(false)
|
|
30
|
+
}, (err) => {
|
|
31
|
+
setError(err as Error)
|
|
32
|
+
setLoading(false)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return () => unsubscribe()
|
|
36
|
+
}, [db, userId])
|
|
37
|
+
|
|
38
|
+
return { user, loading, error }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function useUserRealtime<T>(db: Firestore, collectionName: string, docId: string) {
|
|
42
|
+
const [data, setData] = useState<T | null>(null)
|
|
43
|
+
const [loading, setLoading] = useState(true)
|
|
44
|
+
const [error, setError] = useState<Error | null>(null)
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!docId) {
|
|
48
|
+
setData(null)
|
|
49
|
+
setLoading(false)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const unsubscribe = onSnapshot(doc(db, collectionName, docId), (docSnap) => {
|
|
54
|
+
if (docSnap.exists()) {
|
|
55
|
+
setData(docSnap.data() as T)
|
|
56
|
+
} else {
|
|
57
|
+
setData(null)
|
|
58
|
+
}
|
|
59
|
+
setLoading(false)
|
|
60
|
+
}, (err) => {
|
|
61
|
+
setError(err as Error)
|
|
62
|
+
setLoading(false)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
return () => unsubscribe()
|
|
66
|
+
}, [db, collectionName, docId])
|
|
67
|
+
|
|
68
|
+
return { data, loading, error }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function useCollectionRealtime<T>(
|
|
72
|
+
db: Firestore,
|
|
73
|
+
collectionName: string,
|
|
74
|
+
constraints?: any[]
|
|
75
|
+
) {
|
|
76
|
+
const [data, setData] = useState<T[]>([])
|
|
77
|
+
const [loading, setLoading] = useState(true)
|
|
78
|
+
const [error, setError] = useState<Error | null>(null)
|
|
79
|
+
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const q = constraints
|
|
82
|
+
? query(collection(db, collectionName), ...constraints)
|
|
83
|
+
: collection(db, collectionName)
|
|
84
|
+
|
|
85
|
+
const unsubscribe = onSnapshot(q, (querySnap: QuerySnapshot<DocumentData>) => {
|
|
86
|
+
const items = querySnap.docs.map((doc) => ({ id: doc.id, ...doc.data() } as T))
|
|
87
|
+
setData(items)
|
|
88
|
+
setLoading(false)
|
|
89
|
+
}, (err) => {
|
|
90
|
+
setError(err as Error)
|
|
91
|
+
setLoading(false)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
return () => unsubscribe()
|
|
95
|
+
}, [db, collectionName, JSON.stringify(constraints)])
|
|
96
|
+
|
|
97
|
+
return { data, loading, error }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function useDocumentValue<T>(
|
|
101
|
+
db: Firestore,
|
|
102
|
+
collectionName: string,
|
|
103
|
+
docId: string
|
|
104
|
+
) {
|
|
105
|
+
const [data, setData] = useState<T | null>(null)
|
|
106
|
+
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (!docId) {
|
|
109
|
+
setData(null)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const unsubscribe = onSnapshot(doc(db, collectionName, docId), (docSnap) => {
|
|
114
|
+
if (docSnap.exists()) {
|
|
115
|
+
setData(docSnap.data() as T)
|
|
116
|
+
} else {
|
|
117
|
+
setData(null)
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
return () => unsubscribe()
|
|
122
|
+
}, [db, collectionName, docId])
|
|
123
|
+
|
|
124
|
+
return data
|
|
125
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage Hook
|
|
3
|
+
* @description React hook for file storage operations
|
|
4
|
+
* Migrated from: /Users/umituz/Desktop/github/umituz/apps/web/app-growth-factory/src/domains/firebase/hooks/useStorage.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useCallback } from 'react'
|
|
8
|
+
import type { IFileRepository } from '../../domain/interfaces/file.repository.interface'
|
|
9
|
+
import type { UploadProgress, UploadResult } from '../../domain/entities/file.entity'
|
|
10
|
+
|
|
11
|
+
export interface UseStorageOptions {
|
|
12
|
+
fileRepository: IFileRepository
|
|
13
|
+
userId: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useStorage({ fileRepository, userId }: UseStorageOptions) {
|
|
17
|
+
const [uploading, setUploading] = useState(false)
|
|
18
|
+
const [progress, setProgress] = useState(0)
|
|
19
|
+
const [error, setError] = useState<Error | null>(null)
|
|
20
|
+
|
|
21
|
+
const uploadFile = useCallback(
|
|
22
|
+
async (path: string, file: File): Promise<UploadResult> => {
|
|
23
|
+
setUploading(true)
|
|
24
|
+
setProgress(0)
|
|
25
|
+
setError(null)
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const result = await fileRepository.uploadFile(userId, path, file, {
|
|
29
|
+
onProgress: (progress: UploadProgress) => {
|
|
30
|
+
setProgress(progress.progress)
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
setProgress(100)
|
|
35
|
+
return result
|
|
36
|
+
} catch (err) {
|
|
37
|
+
setError(err as Error)
|
|
38
|
+
throw err
|
|
39
|
+
} finally {
|
|
40
|
+
setUploading(false)
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
[fileRepository, userId]
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
const uploadImage = useCallback(
|
|
47
|
+
async (file: File): Promise<UploadResult> => {
|
|
48
|
+
return uploadFile('images', file)
|
|
49
|
+
},
|
|
50
|
+
[uploadFile]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
const uploadVideo = useCallback(
|
|
54
|
+
async (file: File): Promise<UploadResult> => {
|
|
55
|
+
return uploadFile('videos', file)
|
|
56
|
+
},
|
|
57
|
+
[uploadFile]
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
const uploadDocument = useCallback(
|
|
61
|
+
async (file: File): Promise<UploadResult> => {
|
|
62
|
+
return uploadFile('documents', file)
|
|
63
|
+
},
|
|
64
|
+
[uploadFile]
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
const uploadProfilePicture = useCallback(
|
|
68
|
+
async (file: File): Promise<UploadResult> => {
|
|
69
|
+
setUploading(true)
|
|
70
|
+
setError(null)
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const result = await fileRepository.uploadProfilePicture(userId, file)
|
|
74
|
+
return result
|
|
75
|
+
} catch (err) {
|
|
76
|
+
setError(err as Error)
|
|
77
|
+
throw err
|
|
78
|
+
} finally {
|
|
79
|
+
setUploading(false)
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
[fileRepository, userId]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
const deleteFile = useCallback(
|
|
86
|
+
async (path: string): Promise<void> => {
|
|
87
|
+
setUploading(true)
|
|
88
|
+
setError(null)
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await fileRepository.deleteFile(path)
|
|
92
|
+
} catch (err) {
|
|
93
|
+
setError(err as Error)
|
|
94
|
+
throw err
|
|
95
|
+
} finally {
|
|
96
|
+
setUploading(false)
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
[fileRepository]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
const listUserImages = useCallback(
|
|
103
|
+
async (): Promise<string[]> => {
|
|
104
|
+
setError(null)
|
|
105
|
+
try {
|
|
106
|
+
return await fileRepository.listUserImages(userId)
|
|
107
|
+
} catch (err) {
|
|
108
|
+
setError(err as Error)
|
|
109
|
+
throw err
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
[fileRepository, userId]
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
const listUserVideos = useCallback(
|
|
116
|
+
async (): Promise<string[]> => {
|
|
117
|
+
setError(null)
|
|
118
|
+
try {
|
|
119
|
+
return await fileRepository.listUserVideos(userId)
|
|
120
|
+
} catch (err) {
|
|
121
|
+
setError(err as Error)
|
|
122
|
+
throw err
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
[fileRepository, userId]
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
uploadFile,
|
|
130
|
+
uploadImage,
|
|
131
|
+
uploadVideo,
|
|
132
|
+
uploadDocument,
|
|
133
|
+
uploadProfilePicture,
|
|
134
|
+
deleteFile,
|
|
135
|
+
listUserImages,
|
|
136
|
+
listUserVideos,
|
|
137
|
+
uploading,
|
|
138
|
+
progress,
|
|
139
|
+
error,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FirebaseProvider
|
|
3
|
+
* @description React Context Provider for Firebase initialization
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createContext, useContext, type ReactNode } from 'react'
|
|
7
|
+
import type { FirebaseInstances } from '../../infrastructure/firebase/client'
|
|
8
|
+
import { getFirebaseInstances } from '../../infrastructure/firebase/client'
|
|
9
|
+
|
|
10
|
+
interface FirebaseContextValue {
|
|
11
|
+
instances: FirebaseInstances
|
|
12
|
+
isInitialized: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const FirebaseContext = createContext<FirebaseContextValue | null>(null)
|
|
16
|
+
|
|
17
|
+
export interface FirebaseProviderProps {
|
|
18
|
+
children: ReactNode
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function FirebaseProvider({ children }: FirebaseProviderProps) {
|
|
22
|
+
// Note: Firebase is already initialized via environment variables
|
|
23
|
+
// This provider just makes instances available via context
|
|
24
|
+
const instances = getFirebaseInstances()
|
|
25
|
+
|
|
26
|
+
const value: FirebaseContextValue = {
|
|
27
|
+
instances,
|
|
28
|
+
isInitialized: true,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return <FirebaseContext.Provider value={value}>{children}</FirebaseContext.Provider>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function useFirebaseContext() {
|
|
35
|
+
const context = useContext(FirebaseContext)
|
|
36
|
+
if (!context) {
|
|
37
|
+
throw new Error('useFirebaseContext must be used within FirebaseProvider')
|
|
38
|
+
}
|
|
39
|
+
return context
|
|
40
|
+
}
|