@umituz/web-firebase 2.1.1 → 3.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/package.json +12 -39
- package/src/domain/interfaces/auth.repository.interface.ts +4 -19
- package/src/domain/interfaces/file.repository.interface.ts +5 -102
- package/src/domain/interfaces/index.ts +7 -0
- package/src/domain/interfaces/user.repository.interface.ts +17 -44
- package/src/domains/auth/entities/index.ts +60 -0
- package/src/domains/auth/index.ts +13 -0
- package/src/domains/auth/services/auth.service.ts +245 -0
- package/src/domains/auth/services/index.ts +7 -0
- package/src/domains/auth/types/auth-service.interface.ts +72 -0
- package/src/domains/auth/types/index.ts +5 -0
- package/src/domains/firestore/entities/index.ts +82 -0
- package/src/domains/firestore/index.ts +13 -0
- package/src/domains/firestore/services/firestore.service.ts +191 -0
- package/src/domains/firestore/services/index.ts +7 -0
- package/src/domains/firestore/types/firestore-service.interface.ts +64 -0
- package/src/domains/firestore/types/index.ts +5 -0
- package/src/domains/storage/entities/index.ts +94 -0
- package/src/domains/storage/index.ts +13 -0
- package/src/domains/storage/services/index.ts +7 -0
- package/src/domains/storage/services/storage.service.ts +223 -0
- package/src/domains/storage/types/index.ts +5 -0
- package/src/domains/storage/types/storage-service.interface.ts +120 -0
- package/src/index.ts +12 -16
- package/src/presentation/hooks/useAuth.ts +69 -26
- package/src/presentation/providers/FirebaseProvider.tsx +9 -14
- package/dist/application/index.d.mts +0 -273
- package/dist/application/index.d.ts +0 -273
- package/dist/application/index.js +0 -490
- package/dist/application/index.mjs +0 -19
- package/dist/chunk-34DL2QWQ.mjs +0 -87
- package/dist/chunk-4FP2ELQ5.mjs +0 -96
- package/dist/chunk-7TX3OU3O.mjs +0 -721
- package/dist/chunk-I6WGBPFB.mjs +0 -439
- package/dist/chunk-RZ4QR6TB.mjs +0 -96
- package/dist/chunk-U2XI4MGO.mjs +0 -397
- package/dist/domain/index.d.mts +0 -325
- package/dist/domain/index.d.ts +0 -325
- package/dist/domain/index.js +0 -662
- package/dist/domain/index.mjs +0 -36
- package/dist/file.repository.interface-v5vHgVsZ.d.mts +0 -241
- package/dist/file.repository.interface-v5vHgVsZ.d.ts +0 -241
- package/dist/firebase.entity-xvfEPjXZ.d.mts +0 -15
- package/dist/firebase.entity-xvfEPjXZ.d.ts +0 -15
- package/dist/index.d.mts +0 -14
- package/dist/index.d.ts +0 -14
- package/dist/index.js +0 -1833
- package/dist/index.mjs +0 -98
- package/dist/infrastructure/index.d.mts +0 -170
- package/dist/infrastructure/index.d.ts +0 -170
- package/dist/infrastructure/index.js +0 -856
- package/dist/infrastructure/index.mjs +0 -46
- package/dist/presentation/index.d.mts +0 -25
- package/dist/presentation/index.d.ts +0 -25
- package/dist/presentation/index.js +0 -105
- package/dist/presentation/index.mjs +0 -6
- package/dist/user.repository.interface-DS74TsJ5.d.mts +0 -298
- package/dist/user.repository.interface-DS74TsJ5.d.ts +0 -298
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage Service
|
|
3
|
+
* @description Firebase Storage implementation of IStorageService
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
ref,
|
|
8
|
+
uploadBytes,
|
|
9
|
+
uploadBytesResumable,
|
|
10
|
+
getDownloadURL,
|
|
11
|
+
deleteObject,
|
|
12
|
+
listAll,
|
|
13
|
+
getMetadata,
|
|
14
|
+
} from 'firebase/storage'
|
|
15
|
+
import { getFirebaseStorage } from '../../../infrastructure/firebase/client'
|
|
16
|
+
import type { IStorageService } from '../types'
|
|
17
|
+
import type {
|
|
18
|
+
UploadResult,
|
|
19
|
+
UploadOptions,
|
|
20
|
+
} from '../entities'
|
|
21
|
+
|
|
22
|
+
class StorageService implements IStorageService {
|
|
23
|
+
private get storage() {
|
|
24
|
+
return getFirebaseStorage()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Upload Methods
|
|
28
|
+
|
|
29
|
+
async uploadFile(
|
|
30
|
+
userId: string,
|
|
31
|
+
path: string,
|
|
32
|
+
file: File | Blob,
|
|
33
|
+
options?: UploadOptions
|
|
34
|
+
): Promise<UploadResult> {
|
|
35
|
+
const storageRef = ref(this.storage, `users/${userId}/${path}`)
|
|
36
|
+
const uploadTask = uploadBytesResumable(storageRef, file)
|
|
37
|
+
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
uploadTask.on(
|
|
40
|
+
'state_changed',
|
|
41
|
+
(snapshot) => {
|
|
42
|
+
if (options?.onProgress) {
|
|
43
|
+
const progress = {
|
|
44
|
+
bytesTransferred: snapshot.bytesTransferred,
|
|
45
|
+
totalBytes: snapshot.totalBytes,
|
|
46
|
+
progress: (snapshot.bytesTransferred / snapshot.totalBytes) * 100,
|
|
47
|
+
state: 'running' as const,
|
|
48
|
+
}
|
|
49
|
+
options.onProgress(progress)
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
() => reject(new Error('Upload failed')),
|
|
53
|
+
async () => {
|
|
54
|
+
const downloadURL = await getDownloadURL(uploadTask.snapshot.ref)
|
|
55
|
+
const metadata = await getMetadata(uploadTask.snapshot.ref)
|
|
56
|
+
|
|
57
|
+
resolve({
|
|
58
|
+
id: uploadTask.snapshot.ref.name,
|
|
59
|
+
name: metadata.name || uploadTask.snapshot.ref.name,
|
|
60
|
+
fullPath: metadata.fullPath || uploadTask.snapshot.ref.fullPath,
|
|
61
|
+
downloadURL,
|
|
62
|
+
contentType: metadata.contentType || '',
|
|
63
|
+
size: metadata.size || 0,
|
|
64
|
+
createdAt: metadata.timeCreated ? new Date(metadata.timeCreated).getTime() : Date.now(),
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async uploadImage(userId: string, file: File, filename?: string): Promise<UploadResult> {
|
|
72
|
+
const name = filename || `${Date.now()}_${file.name}`
|
|
73
|
+
return this.uploadFile(userId, `images/${name}`, file)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async uploadVideo(userId: string, file: File, filename?: string): Promise<UploadResult> {
|
|
77
|
+
const name = filename || `${Date.now()}_${file.name}`
|
|
78
|
+
return this.uploadFile(userId, `videos/${name}`, file)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async uploadDocument(userId: string, file: File, filename?: string): Promise<UploadResult> {
|
|
82
|
+
const name = filename || `${Date.now()}_${file.name}`
|
|
83
|
+
return this.uploadFile(userId, `documents/${name}`, file)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async uploadProfilePicture(userId: string, file: File): Promise<UploadResult> {
|
|
87
|
+
const storageRef = ref(this.storage, `users/${userId}/profile/${Date.now()}_${file.name}`)
|
|
88
|
+
await uploadBytes(storageRef, file)
|
|
89
|
+
const downloadURL = await getDownloadURL(storageRef)
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
id: storageRef.name,
|
|
93
|
+
name: file.name,
|
|
94
|
+
fullPath: storageRef.fullPath,
|
|
95
|
+
downloadURL,
|
|
96
|
+
contentType: file.type,
|
|
97
|
+
size: file.size,
|
|
98
|
+
createdAt: Date.now(),
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Download Methods
|
|
103
|
+
|
|
104
|
+
async getDownloadURL(path: string): Promise<string> {
|
|
105
|
+
try {
|
|
106
|
+
const storageRef = ref(this.storage, path)
|
|
107
|
+
return await getDownloadURL(storageRef)
|
|
108
|
+
} catch {
|
|
109
|
+
throw new Error('File not found')
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Delete Methods
|
|
114
|
+
|
|
115
|
+
async deleteFile(path: string): Promise<void> {
|
|
116
|
+
try {
|
|
117
|
+
const storageRef = ref(this.storage, path)
|
|
118
|
+
await deleteObject(storageRef)
|
|
119
|
+
} catch {
|
|
120
|
+
throw new Error('File not found')
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async deleteUserFiles(userId: string): Promise<void> {
|
|
125
|
+
try {
|
|
126
|
+
const userRef = ref(this.storage, `users/${userId}`)
|
|
127
|
+
const result = await listAll(userRef)
|
|
128
|
+
|
|
129
|
+
// Delete all files in all prefixes
|
|
130
|
+
for (const prefix of result.prefixes) {
|
|
131
|
+
const prefixResult = await listAll(prefix)
|
|
132
|
+
await Promise.all(prefixResult.items.map((item) => deleteObject(item)))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Delete all files in root
|
|
136
|
+
await Promise.all(result.items.map((item) => deleteObject(item)))
|
|
137
|
+
} catch {
|
|
138
|
+
throw new Error('Failed to delete user files')
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async deleteImage(userId: string, filename: string): Promise<void> {
|
|
143
|
+
await this.deleteFile(`users/${userId}/images/${filename}`)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async deleteVideo(userId: string, filename: string): Promise<void> {
|
|
147
|
+
await this.deleteFile(`users/${userId}/videos/${filename}`)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async deleteProfilePicture(userId: string, filename: string): Promise<void> {
|
|
151
|
+
await this.deleteFile(`users/${userId}/profile/${filename}`)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// List Methods
|
|
155
|
+
|
|
156
|
+
async listUserFiles(userId: string, path?: string): Promise<string[]> {
|
|
157
|
+
const userRef = ref(this.storage, path ? `users/${userId}/${path}` : `users/${userId}`)
|
|
158
|
+
const result = await listAll(userRef)
|
|
159
|
+
|
|
160
|
+
const urls = await Promise.all(result.items.map((item) => getDownloadURL(item)))
|
|
161
|
+
return urls
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async listUserImages(userId: string): Promise<string[]> {
|
|
165
|
+
return this.listUserFiles(userId, 'images')
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async listUserVideos(userId: string): Promise<string[]> {
|
|
169
|
+
return this.listUserFiles(userId, 'videos')
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Validation
|
|
173
|
+
|
|
174
|
+
validateFile(file: File, options?: { maxSizeBytes?: number; maxSizeMB?: number; allowedTypes?: string[] }): boolean {
|
|
175
|
+
const maxSizeBytes = options?.maxSizeBytes || (options?.maxSizeMB ? options.maxSizeMB * 1024 * 1024 : 10 * 1024 * 1024)
|
|
176
|
+
|
|
177
|
+
if (file.size > maxSizeBytes) {
|
|
178
|
+
return false
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (options?.allowedTypes && !options.allowedTypes.includes(file.type)) {
|
|
182
|
+
return false
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
isImageFile(file: File): boolean {
|
|
189
|
+
return file.type.startsWith('image/')
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
isVideoFile(file: File): boolean {
|
|
193
|
+
return file.type.startsWith('video/')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
isDocumentFile(file: File): boolean {
|
|
197
|
+
const docTypes = [
|
|
198
|
+
'application/pdf',
|
|
199
|
+
'application/msword',
|
|
200
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
201
|
+
'application/vnd.ms-excel',
|
|
202
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
203
|
+
'text/plain',
|
|
204
|
+
]
|
|
205
|
+
return docTypes.includes(file.type)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Utility Methods
|
|
209
|
+
|
|
210
|
+
generateUniqueFilename(originalName: string): string {
|
|
211
|
+
const timestamp = Date.now()
|
|
212
|
+
const random = Math.random().toString(36).substring(2, 8)
|
|
213
|
+
const extension = this.getFileExtension(originalName)
|
|
214
|
+
return `${timestamp}_${random}.${extension}`
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
getFileExtension(filename: string): string {
|
|
218
|
+
return filename.slice(((filename.lastIndexOf('.') - 1) >>> 0) + 2)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Export class and singleton instance
|
|
223
|
+
export const storageService = new StorageService()
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage Service Interface
|
|
3
|
+
* @description Abstract interface for storage operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
UploadResult,
|
|
8
|
+
UploadOptions,
|
|
9
|
+
} from '../entities'
|
|
10
|
+
|
|
11
|
+
export interface IStorageService {
|
|
12
|
+
/**
|
|
13
|
+
* Upload file to storage
|
|
14
|
+
*/
|
|
15
|
+
uploadFile(
|
|
16
|
+
userId: string,
|
|
17
|
+
path: string,
|
|
18
|
+
file: File | Blob,
|
|
19
|
+
options?: UploadOptions
|
|
20
|
+
): Promise<UploadResult>
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Upload image with automatic categorization
|
|
24
|
+
*/
|
|
25
|
+
uploadImage(userId: string, file: File, filename?: string): Promise<UploadResult>
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Upload video with automatic categorization
|
|
29
|
+
*/
|
|
30
|
+
uploadVideo(userId: string, file: File, filename?: string): Promise<UploadResult>
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Upload document with automatic categorization
|
|
34
|
+
*/
|
|
35
|
+
uploadDocument(userId: string, file: File, filename?: string): Promise<UploadResult>
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Upload profile picture
|
|
39
|
+
*/
|
|
40
|
+
uploadProfilePicture(userId: string, file: File): Promise<UploadResult>
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get download URL for a file
|
|
44
|
+
*/
|
|
45
|
+
getDownloadURL(path: string): Promise<string>
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Delete file by path
|
|
49
|
+
*/
|
|
50
|
+
deleteFile(path: string): Promise<void>
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Delete all user files
|
|
54
|
+
*/
|
|
55
|
+
deleteUserFiles(userId: string): Promise<void>
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Delete user image
|
|
59
|
+
*/
|
|
60
|
+
deleteImage(userId: string, filename: string): Promise<void>
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Delete user video
|
|
64
|
+
*/
|
|
65
|
+
deleteVideo(userId: string, filename: string): Promise<void>
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Delete profile picture
|
|
69
|
+
*/
|
|
70
|
+
deleteProfilePicture(userId: string, filename: string): Promise<void>
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* List user files
|
|
74
|
+
*/
|
|
75
|
+
listUserFiles(userId: string, path?: string): Promise<string[]>
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* List user images
|
|
79
|
+
*/
|
|
80
|
+
listUserImages(userId: string): Promise<string[]>
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* List user videos
|
|
84
|
+
*/
|
|
85
|
+
listUserVideos(userId: string): Promise<string[]>
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Validate file before upload
|
|
89
|
+
*/
|
|
90
|
+
validateFile(file: File, options?: {
|
|
91
|
+
maxSizeBytes?: number
|
|
92
|
+
maxSizeMB?: number
|
|
93
|
+
allowedTypes?: string[]
|
|
94
|
+
}): boolean
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Check if file is an image
|
|
98
|
+
*/
|
|
99
|
+
isImageFile(file: File): boolean
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Check if file is a video
|
|
103
|
+
*/
|
|
104
|
+
isVideoFile(file: File): boolean
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if file is a document
|
|
108
|
+
*/
|
|
109
|
+
isDocumentFile(file: File): boolean
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Generate unique filename
|
|
113
|
+
*/
|
|
114
|
+
generateUniqueFilename(originalName: string): string
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get file extension
|
|
118
|
+
*/
|
|
119
|
+
getFileExtension(filename: string): string
|
|
120
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @umituz/web-firebase
|
|
3
|
-
*
|
|
3
|
+
* Firebase integration for web applications with domain-based architecture
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - "@umituz/web-firebase/infrastructure" - Firebase adapters
|
|
10
|
-
* - "@umituz/web-firebase/presentation" - React hooks and providers
|
|
5
|
+
* Usage:
|
|
6
|
+
* import { AuthService } from '@umituz/web-firebase/auth'
|
|
7
|
+
* import { FirestoreService } from '@umituz/web-firebase/firestore'
|
|
8
|
+
* import { StorageService } from '@umituz/web-firebase/storage'
|
|
11
9
|
*/
|
|
12
10
|
|
|
13
|
-
//
|
|
14
|
-
export * from './
|
|
11
|
+
// Firebase client initialization
|
|
12
|
+
export * from './infrastructure/firebase/client'
|
|
15
13
|
|
|
16
|
-
//
|
|
17
|
-
export
|
|
14
|
+
// React components
|
|
15
|
+
export { FirebaseProvider, useFirebaseContext } from './presentation/providers/FirebaseProvider'
|
|
18
16
|
|
|
19
|
-
//
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
// Presentation hooks and providers
|
|
23
|
-
export * from './presentation'
|
|
17
|
+
// Re-export common types for convenience
|
|
18
|
+
export type { FirebaseInstances } from './infrastructure/firebase/client'
|
|
19
|
+
export type { FirebaseContextValue, FirebaseProviderProps } from './presentation/providers/FirebaseProvider'
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auth Hook
|
|
3
3
|
* @description React hook for authentication operations
|
|
4
|
-
* Uses
|
|
4
|
+
* Uses new domain services - no adapter injection needed
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { useCallback } from 'react'
|
|
7
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
8
8
|
import type { User as FirebaseUser } from 'firebase/auth'
|
|
9
|
-
import type { User } from '../../
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import type { User as FirestoreUser } from '../../domains/firestore/entities'
|
|
10
|
+
import { authService } from '../../domains/auth/services'
|
|
11
|
+
import { firestoreService } from '../../domains/firestore/services'
|
|
12
12
|
import { useFirebaseContext } from '../providers/FirebaseProvider'
|
|
13
13
|
|
|
14
14
|
export interface UseAuthResult {
|
|
15
15
|
firebaseUser: FirebaseUser | null
|
|
16
|
-
user:
|
|
16
|
+
user: FirestoreUser | null
|
|
17
17
|
loading: boolean
|
|
18
18
|
error: Error | null
|
|
19
19
|
isAuthenticated: boolean
|
|
@@ -29,62 +29,106 @@ export interface UseAuthResult {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export function useAuth(): UseAuthResult {
|
|
32
|
-
const { instances,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
const { instances, loading, error } = useFirebaseContext()
|
|
33
|
+
const [user, setUser] = useState<FirestoreUser | null>(null)
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const fetchUser = async () => {
|
|
37
|
+
const currentUser = instances?.auth.currentUser
|
|
38
|
+
if (currentUser) {
|
|
39
|
+
const userData = await firestoreService.getUser(currentUser.uid)
|
|
40
|
+
setUser(userData)
|
|
41
|
+
} else {
|
|
42
|
+
setUser(null)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
fetchUser()
|
|
46
|
+
}, [instances?.auth.currentUser])
|
|
36
47
|
|
|
37
48
|
const signIn = useCallback(async (email: string, password: string) => {
|
|
38
|
-
|
|
49
|
+
const result = await authService.signIn(email, password)
|
|
50
|
+
return result.user
|
|
39
51
|
}, [])
|
|
40
52
|
|
|
41
53
|
const signUp = useCallback(async (email: string, password: string, displayName: string) => {
|
|
42
|
-
const userCredential = await
|
|
54
|
+
const userCredential = await authService.signUp(email, password, displayName)
|
|
43
55
|
|
|
44
56
|
// Create user profile in Firestore
|
|
45
|
-
|
|
57
|
+
const now = Date.now()
|
|
58
|
+
await firestoreService.createUser(userCredential.user.uid, {
|
|
46
59
|
profile: {
|
|
60
|
+
id: userCredential.user.uid,
|
|
47
61
|
email: email,
|
|
48
62
|
displayName: displayName,
|
|
49
|
-
createdAt:
|
|
63
|
+
createdAt: now,
|
|
64
|
+
updatedAt: now,
|
|
65
|
+
lastLoginAt: now,
|
|
66
|
+
emailVerified: false,
|
|
50
67
|
},
|
|
51
68
|
settings: {
|
|
52
69
|
theme: 'light',
|
|
53
70
|
language: 'tr',
|
|
71
|
+
timezone: 'Europe/Istanbul',
|
|
72
|
+
currency: 'TRY',
|
|
73
|
+
notifications: {
|
|
74
|
+
email: true,
|
|
75
|
+
push: true,
|
|
76
|
+
marketing: false,
|
|
77
|
+
security: true,
|
|
78
|
+
weeklyDigest: false,
|
|
79
|
+
},
|
|
80
|
+
privacy: {
|
|
81
|
+
profileVisibility: 'public',
|
|
82
|
+
showEmail: false,
|
|
83
|
+
showPhone: false,
|
|
84
|
+
dataSharing: false,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
subscription: {
|
|
88
|
+
plan: 'free',
|
|
89
|
+
status: 'active',
|
|
90
|
+
cancelAtPeriodEnd: false,
|
|
91
|
+
createdAt: now,
|
|
92
|
+
updatedAt: now,
|
|
54
93
|
},
|
|
55
94
|
})
|
|
56
95
|
|
|
57
96
|
return userCredential.user
|
|
58
97
|
}, [])
|
|
59
98
|
|
|
60
|
-
const
|
|
61
|
-
|
|
99
|
+
const signInWithGoogleCallback = useCallback(async () => {
|
|
100
|
+
const result = await authService.signInWithGoogle()
|
|
101
|
+
return result.user
|
|
62
102
|
}, [])
|
|
63
103
|
|
|
64
104
|
const signOut = useCallback(async () => {
|
|
65
|
-
await
|
|
105
|
+
await authService.signOut()
|
|
66
106
|
}, [])
|
|
67
107
|
|
|
68
108
|
const sendPasswordReset = useCallback(async (email: string) => {
|
|
69
|
-
await
|
|
109
|
+
await authService.sendPasswordReset(email)
|
|
70
110
|
}, [])
|
|
71
111
|
|
|
72
112
|
const resendEmailVerification = useCallback(async () => {
|
|
73
|
-
await
|
|
113
|
+
await authService.resendEmailVerification()
|
|
74
114
|
}, [])
|
|
75
115
|
|
|
76
116
|
const updateProfile = useCallback(async (updates: { displayName?: string; photoURL?: string }) => {
|
|
77
|
-
await
|
|
117
|
+
await authService.updateProfile(updates)
|
|
78
118
|
|
|
79
119
|
// Refresh user data from Firestore if available
|
|
80
|
-
|
|
81
|
-
|
|
120
|
+
const currentUser = instances?.auth.currentUser
|
|
121
|
+
if (currentUser) {
|
|
122
|
+
const userData = await firestoreService.getUser(currentUser.uid)
|
|
123
|
+
setUser(userData)
|
|
82
124
|
}
|
|
83
125
|
}, [instances])
|
|
84
126
|
|
|
85
127
|
const refreshUser = useCallback(async () => {
|
|
86
|
-
|
|
87
|
-
|
|
128
|
+
const currentUser = instances?.auth.currentUser
|
|
129
|
+
if (currentUser) {
|
|
130
|
+
const userData = await firestoreService.getUser(currentUser.uid)
|
|
131
|
+
setUser(userData)
|
|
88
132
|
}
|
|
89
133
|
}, [instances])
|
|
90
134
|
|
|
@@ -97,7 +141,7 @@ export function useAuth(): UseAuthResult {
|
|
|
97
141
|
isEmailVerified: instances?.auth.currentUser?.emailVerified || false,
|
|
98
142
|
signIn,
|
|
99
143
|
signUp,
|
|
100
|
-
signInWithGoogle,
|
|
144
|
+
signInWithGoogle: signInWithGoogleCallback,
|
|
101
145
|
signOut,
|
|
102
146
|
sendPasswordReset,
|
|
103
147
|
resendEmailVerification,
|
|
@@ -105,4 +149,3 @@ export function useAuth(): UseAuthResult {
|
|
|
105
149
|
refreshUser,
|
|
106
150
|
}
|
|
107
151
|
}
|
|
108
|
-
|
|
@@ -7,12 +7,14 @@ import { createContext, useContext, useState, useEffect, type ReactNode } from '
|
|
|
7
7
|
import type { User } from 'firebase/auth'
|
|
8
8
|
import type { FirebaseInstances } from '../../infrastructure/firebase/client'
|
|
9
9
|
import { getFirebaseInstances } from '../../infrastructure/firebase/client'
|
|
10
|
-
import {
|
|
10
|
+
import type { AuthUser } from '../../domains/auth/entities'
|
|
11
|
+
import { authService } from '../../domains/auth/services'
|
|
11
12
|
|
|
12
13
|
export interface FirebaseContextValue {
|
|
13
14
|
instances: FirebaseInstances
|
|
14
15
|
isInitialized: boolean
|
|
15
16
|
user: User | null
|
|
17
|
+
authUser: AuthUser | null
|
|
16
18
|
loading: boolean
|
|
17
19
|
error: Error | null
|
|
18
20
|
}
|
|
@@ -34,6 +36,7 @@ export interface FirebaseProviderProps {
|
|
|
34
36
|
export function FirebaseProvider({ children, config }: FirebaseProviderProps) {
|
|
35
37
|
const [instances, setInstances] = useState<FirebaseInstances | null>(null)
|
|
36
38
|
const [user, setUser] = useState<User | null>(null)
|
|
39
|
+
const [authUser, setAuthUser] = useState<AuthUser | null>(null)
|
|
37
40
|
const [loading, setLoading] = useState(true)
|
|
38
41
|
const [error, setError] = useState<Error | null>(null)
|
|
39
42
|
|
|
@@ -44,10 +47,10 @@ export function FirebaseProvider({ children, config }: FirebaseProviderProps) {
|
|
|
44
47
|
setInstances(firebaseInstances)
|
|
45
48
|
|
|
46
49
|
// Set up auth state listener
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
setUser(
|
|
50
|
+
const unsubscribe = authService.onAuthStateChanged(
|
|
51
|
+
(newAuthUser) => {
|
|
52
|
+
setAuthUser(newAuthUser)
|
|
53
|
+
setUser(newAuthUser ? (firebaseInstances.auth.currentUser || null) : null)
|
|
51
54
|
setLoading(false)
|
|
52
55
|
setError(null)
|
|
53
56
|
},
|
|
@@ -70,18 +73,11 @@ export function FirebaseProvider({ children, config }: FirebaseProviderProps) {
|
|
|
70
73
|
instances: instances!,
|
|
71
74
|
isInitialized: !!instances,
|
|
72
75
|
user,
|
|
76
|
+
authUser,
|
|
73
77
|
loading,
|
|
74
78
|
error,
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
if (!instances || loading) {
|
|
78
|
-
return (
|
|
79
|
-
<FirebaseContext.Provider value={{ ...value, isInitialized: false }}>
|
|
80
|
-
{children}
|
|
81
|
-
</FirebaseContext.Provider>
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
81
|
return <FirebaseContext.Provider value={value}>{children}</FirebaseContext.Provider>
|
|
86
82
|
}
|
|
87
83
|
|
|
@@ -92,4 +88,3 @@ export function useFirebaseContext(): FirebaseContextValue {
|
|
|
92
88
|
}
|
|
93
89
|
return context
|
|
94
90
|
}
|
|
95
|
-
|