@umituz/react-native-auth 3.4.30 → 3.4.32
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 +426 -0
- package/package.json +1 -3
- package/src/application/README.md +513 -0
- package/src/domain/ConfigAndErrors.md +545 -0
- package/src/domain/README.md +293 -0
- package/src/domain/entities/AuthUser.md +377 -0
- package/src/domain/entities/UserProfile.md +443 -0
- package/src/infrastructure/README.md +576 -0
- package/src/infrastructure/services/README.md +417 -0
- package/src/presentation/README.md +770 -0
- package/src/presentation/components/AuthBackground.tsx +21 -0
- package/src/presentation/components/AuthContainer.tsx +3 -3
- package/src/presentation/components/LoginForm.md +222 -0
- package/src/presentation/components/PasswordIndicators.md +260 -0
- package/src/presentation/components/ProfileComponents.md +575 -0
- package/src/presentation/components/README.md +117 -0
- package/src/presentation/components/SocialLoginButtons.md +340 -0
- package/src/presentation/hooks/README.md +122 -0
- package/src/presentation/hooks/useAccountManagement.md +386 -0
- package/src/presentation/hooks/useAuth.md +255 -0
- package/src/presentation/hooks/useAuthBottomSheet.md +414 -0
- package/src/presentation/hooks/useAuthRequired.md +248 -0
- package/src/presentation/hooks/useProfileUpdate.md +327 -0
- package/src/presentation/hooks/useSocialLogin.md +356 -0
- package/src/presentation/hooks/useUserProfile.md +230 -0
- package/src/presentation/screens/README.md +198 -0
- package/src/presentation/components/AuthGradientBackground.tsx +0 -33
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# UserProfile Entity
|
|
2
|
+
|
|
3
|
+
Kullanıcı profili entity'si. Firestore'da saklanan kullanıcı bilgilerini temsil eder.
|
|
4
|
+
|
|
5
|
+
## Tip Tanımları
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import type { UserProfile, UpdateProfileParams } from '@umituz/react-native-auth';
|
|
9
|
+
|
|
10
|
+
interface UserProfile {
|
|
11
|
+
uid: string; // Kullanıcı ID'si
|
|
12
|
+
email: string | null; // Email adresi
|
|
13
|
+
displayName: string | null; // Görünen ad
|
|
14
|
+
photoURL: string | null; // Profil fotoğrafı URL'si
|
|
15
|
+
isAnonymous: boolean; // Anonymous kullanıcı mı
|
|
16
|
+
createdAt: Date | null; // Hesap oluşturma tarihi
|
|
17
|
+
lastLoginAt: Date | null; // Son giriş tarihi
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface UpdateProfileParams {
|
|
21
|
+
displayName?: string; // Yeni görünen ad
|
|
22
|
+
photoURL?: string; // Yeni profil fotoğrafı URL'si
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Örnekler
|
|
27
|
+
|
|
28
|
+
### Tam Profil
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
const fullProfile: UserProfile = {
|
|
32
|
+
uid: 'user-123',
|
|
33
|
+
email: 'john@example.com',
|
|
34
|
+
displayName: 'John Doe',
|
|
35
|
+
photoURL: 'https://example.com/avatar.jpg',
|
|
36
|
+
isAnonymous: false,
|
|
37
|
+
createdAt: new Date('2024-01-01'),
|
|
38
|
+
lastLoginAt: new Date('2024-01-15'),
|
|
39
|
+
};
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Minimal Profil
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
const minimalProfile: UserProfile = {
|
|
46
|
+
uid: 'user-456',
|
|
47
|
+
email: 'jane@example.com',
|
|
48
|
+
displayName: null,
|
|
49
|
+
photoURL: null,
|
|
50
|
+
isAnonymous: false,
|
|
51
|
+
createdAt: null,
|
|
52
|
+
lastLoginAt: null,
|
|
53
|
+
};
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Anonymous Profil
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const anonymousProfile: UserProfile = {
|
|
60
|
+
uid: 'anon-789',
|
|
61
|
+
email: null,
|
|
62
|
+
displayName: null,
|
|
63
|
+
photoURL: null,
|
|
64
|
+
isAnonymous: true,
|
|
65
|
+
createdAt: new Date(),
|
|
66
|
+
lastLoginAt: new Date(),
|
|
67
|
+
};
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Kullanım
|
|
71
|
+
|
|
72
|
+
### Profil Oluşturma
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { doc, setDoc, serverTimestamp } from 'firebase/firestore';
|
|
76
|
+
import type { UserProfile } from '@umituz/react-native-auth';
|
|
77
|
+
|
|
78
|
+
async function createUserProfile(uid: string, email: string): Promise<void> {
|
|
79
|
+
const profile: UserProfile = {
|
|
80
|
+
uid,
|
|
81
|
+
email,
|
|
82
|
+
displayName: null,
|
|
83
|
+
photoURL: null,
|
|
84
|
+
isAnonymous: false,
|
|
85
|
+
createdAt: new Date(),
|
|
86
|
+
lastLoginAt: new Date(),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
await setDoc(doc(db, 'users', uid), {
|
|
90
|
+
...profile,
|
|
91
|
+
createdAt: serverTimestamp(),
|
|
92
|
+
lastLoginAt: serverTimestamp(),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Profil Güncelleme
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { doc, updateDoc } from 'firebase/firestore';
|
|
101
|
+
import type { UpdateProfileParams } from '@umituz/react-native-auth';
|
|
102
|
+
|
|
103
|
+
async function updateUserProfile(
|
|
104
|
+
uid: string,
|
|
105
|
+
updates: UpdateProfileParams
|
|
106
|
+
): Promise<void> {
|
|
107
|
+
const updateData: any = {};
|
|
108
|
+
|
|
109
|
+
if (updates.displayName !== undefined) {
|
|
110
|
+
updateData.displayName = updates.displayName;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (updates.photoURL !== undefined) {
|
|
114
|
+
updateData.photoURL = updates.photoURL;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
updateData.updatedAt = serverTimestamp();
|
|
118
|
+
|
|
119
|
+
await updateDoc(doc(db, 'users', uid), updateData);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Kullanım
|
|
123
|
+
await updateUserProfile('user-123', {
|
|
124
|
+
displayName: 'Jane Smith',
|
|
125
|
+
photoURL: 'https://example.com/new-avatar.jpg',
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Profil Okuma
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { doc, getDoc } from 'firebase/firestore';
|
|
133
|
+
import type { UserProfile } from '@umituz/react-native-auth';
|
|
134
|
+
|
|
135
|
+
async function getUserProfile(uid: string): Promise<UserProfile | null> {
|
|
136
|
+
const docRef = doc(db, 'users', uid);
|
|
137
|
+
const docSnap = await getDoc(docRef);
|
|
138
|
+
|
|
139
|
+
if (!docSnap.exists()) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const data = docSnap.data();
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
uid: data.uid,
|
|
147
|
+
email: data.email,
|
|
148
|
+
displayName: data.displayName,
|
|
149
|
+
photoURL: data.photoURL,
|
|
150
|
+
isAnonymous: data.isAnonymous,
|
|
151
|
+
createdAt: data.createdAt?.toDate() || null,
|
|
152
|
+
lastLoginAt: data.lastLoginAt?.toDate() || null,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### AuthUser'dan UserProfile'a Dönüşüm
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import type { AuthUser, UserProfile } from '@umituz/react-native-auth';
|
|
161
|
+
|
|
162
|
+
function authUserToProfile(authUser: AuthUser): UserProfile {
|
|
163
|
+
return {
|
|
164
|
+
uid: authUser.uid,
|
|
165
|
+
email: authUser.email,
|
|
166
|
+
displayName: authUser.displayName,
|
|
167
|
+
photoURL: authUser.photoURL,
|
|
168
|
+
isAnonymous: authUser.isAnonymous,
|
|
169
|
+
createdAt: null, // Firestore'dan gelecek
|
|
170
|
+
lastLoginAt: new Date(),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Validasyon
|
|
176
|
+
|
|
177
|
+
### DisplayName Validasyonu
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
function validateDisplayName(displayName: string): {
|
|
181
|
+
valid: boolean;
|
|
182
|
+
error?: string;
|
|
183
|
+
} {
|
|
184
|
+
if (displayName.length < 2) {
|
|
185
|
+
return { valid: false, error: 'Display name en az 2 karakter olmalı' };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (displayName.length > 50) {
|
|
189
|
+
return { valid: false, error: 'Display name en fazla 50 karakter olabilir' };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return { valid: true };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Kullanım
|
|
196
|
+
const result = validateDisplayName('John');
|
|
197
|
+
if (!result.valid) {
|
|
198
|
+
console.error(result.error);
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### PhotoURL Validasyonu
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
function validatePhotoURL(url: string): {
|
|
206
|
+
valid: boolean;
|
|
207
|
+
error?: string;
|
|
208
|
+
} {
|
|
209
|
+
try {
|
|
210
|
+
const parsed = new URL(url);
|
|
211
|
+
|
|
212
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
213
|
+
return { valid: false, error: 'URL http veya https olmalı' };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Image file extension kontrolü
|
|
217
|
+
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
|
|
218
|
+
const hasImageExtension = imageExtensions.some(ext =>
|
|
219
|
+
parsed.pathname.toLowerCase().endsWith(ext)
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
if (!hasImageExtension) {
|
|
223
|
+
return { valid: false, error: 'Geçersiz resim formatı' };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return { valid: true };
|
|
227
|
+
} catch {
|
|
228
|
+
return { valid: false, error: 'Geçersiz URL' };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Update Params Validasyonu
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
function validateUpdateParams(params: UpdateProfileParams): {
|
|
237
|
+
valid: boolean;
|
|
238
|
+
errors: string[];
|
|
239
|
+
} {
|
|
240
|
+
const errors: string[] = [];
|
|
241
|
+
|
|
242
|
+
if (params.displayName !== undefined) {
|
|
243
|
+
const nameResult = validateDisplayName(params.displayName);
|
|
244
|
+
if (!nameResult.valid) {
|
|
245
|
+
errors.push(nameResult.error!);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (params.photoURL !== undefined) {
|
|
250
|
+
const photoResult = validatePhotoURL(params.photoURL);
|
|
251
|
+
if (!photoResult.valid) {
|
|
252
|
+
errors.push(photoResult.error!);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
valid: errors.length === 0,
|
|
258
|
+
errors,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Profil Photo Yükleme
|
|
264
|
+
|
|
265
|
+
### Firebase Storage ile Profil Fotoğrafı Yükleme
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';
|
|
269
|
+
import * as ImagePicker from 'expo-image-picker';
|
|
270
|
+
|
|
271
|
+
async function uploadProfilePhoto(
|
|
272
|
+
uid: string,
|
|
273
|
+
uri: string
|
|
274
|
+
): Promise<string> {
|
|
275
|
+
// Dosyayı blob'a çevir
|
|
276
|
+
const response = await fetch(uri);
|
|
277
|
+
const blob = await response.blob();
|
|
278
|
+
|
|
279
|
+
// Storage referansı oluştur
|
|
280
|
+
const storageRef = ref(storage, `avatars/${uid}/${Date.now()}.jpg`);
|
|
281
|
+
|
|
282
|
+
// Yükle
|
|
283
|
+
await uploadBytes(storageRef, blob);
|
|
284
|
+
|
|
285
|
+
// URL al
|
|
286
|
+
const downloadURL = await getDownloadURL(storageRef);
|
|
287
|
+
|
|
288
|
+
return downloadURL;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Kullanım
|
|
292
|
+
async function handleProfilePhotoUpload(uid: string) {
|
|
293
|
+
// Resim seç
|
|
294
|
+
const result = await ImagePicker.launchImageLibraryAsync({
|
|
295
|
+
mediaTypes: ['images'],
|
|
296
|
+
allowsEditing: true,
|
|
297
|
+
aspect: [1, 1],
|
|
298
|
+
quality: 0.8,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
if (result.canceled) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Yükle
|
|
306
|
+
const photoURL = await uploadProfilePhoto(uid, result.assets[0].uri);
|
|
307
|
+
|
|
308
|
+
// Profili güncelle
|
|
309
|
+
await updateUserProfile(uid, { photoURL });
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Profil Tamamlama Kontrolü
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
function isProfileComplete(profile: UserProfile): boolean {
|
|
317
|
+
return !!(
|
|
318
|
+
profile.displayName &&
|
|
319
|
+
profile.email &&
|
|
320
|
+
profile.photoURL
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function getProfileCompleteness(profile: UserProfile): {
|
|
325
|
+
percentage: number;
|
|
326
|
+
missing: string[];
|
|
327
|
+
} {
|
|
328
|
+
const required: Array<keyof UserProfile> = [
|
|
329
|
+
'displayName',
|
|
330
|
+
'email',
|
|
331
|
+
'photoURL',
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
const completed = required.filter(field => !!profile[field]);
|
|
335
|
+
const percentage = (completed.length / required.length) * 100;
|
|
336
|
+
|
|
337
|
+
const missing = required.filter(field => !profile[field]);
|
|
338
|
+
|
|
339
|
+
return { percentage, missing };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Kullanım
|
|
343
|
+
const completeness = getProfileCompleteness(profile);
|
|
344
|
+
console.log(`Profil %${completeness.percentage} tamamlandı`);
|
|
345
|
+
console.log('Eksik:', completeness.missing); // ['displayName', 'photoURL']
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Profil İsim Görüntüleme
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
function getProfileDisplayName(profile: UserProfile): string {
|
|
352
|
+
if (profile.displayName) {
|
|
353
|
+
return profile.displayName;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (profile.email) {
|
|
357
|
+
const emailName = profile.email.split('@')[0];
|
|
358
|
+
return emailName.charAt(0).toUpperCase() + emailName.slice(1);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (profile.isAnonymous) {
|
|
362
|
+
return 'Misafir';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return 'Kullanıcı';
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function getProfileInitials(profile: UserProfile): string {
|
|
369
|
+
if (profile.displayName) {
|
|
370
|
+
const names = profile.displayName.trim().split(' ');
|
|
371
|
+
if (names.length >= 2) {
|
|
372
|
+
return (names[0][0] + names[names.length - 1][0]).toUpperCase();
|
|
373
|
+
}
|
|
374
|
+
return names[0][0].toUpperCase();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (profile.email) {
|
|
378
|
+
return profile.email[0].toUpperCase();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return '?';
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
## Profil Meta Verileri
|
|
386
|
+
|
|
387
|
+
### Extra Fields Ekleme
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
interface ExtendedUserProfile extends UserProfile {
|
|
391
|
+
bio?: string;
|
|
392
|
+
location?: string;
|
|
393
|
+
website?: string;
|
|
394
|
+
phoneNumber?: string;
|
|
395
|
+
dateOfBirth?: Date;
|
|
396
|
+
gender?: 'male' | 'female' | 'other' | 'prefer_not_to_say';
|
|
397
|
+
preferences?: {
|
|
398
|
+
newsletter: boolean;
|
|
399
|
+
notifications: boolean;
|
|
400
|
+
language: string;
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Kullanım
|
|
405
|
+
const extendedProfile: ExtendedUserProfile = {
|
|
406
|
+
...baseProfile,
|
|
407
|
+
bio: 'Software developer',
|
|
408
|
+
location: 'Istanbul, Turkey',
|
|
409
|
+
website: 'https://johndoe.com',
|
|
410
|
+
preferences: {
|
|
411
|
+
newsletter: true,
|
|
412
|
+
notifications: true,
|
|
413
|
+
language: 'tr',
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Firestore Index'leri
|
|
419
|
+
|
|
420
|
+
```json
|
|
421
|
+
{
|
|
422
|
+
"indexes": [
|
|
423
|
+
{
|
|
424
|
+
"collectionGroup": "users",
|
|
425
|
+
"queryScope": "COLLECTION",
|
|
426
|
+
"fields": [
|
|
427
|
+
{ "fieldPath": "displayName", "order": "ASCENDING" },
|
|
428
|
+
{ "fieldPath": "createdAt", "order": "DESCENDING" }
|
|
429
|
+
]
|
|
430
|
+
}
|
|
431
|
+
]
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## İlgili Entity'ler
|
|
436
|
+
|
|
437
|
+
- **[`AuthUser`](./AuthUser.md)** - Authentication kullanıcı entity'si
|
|
438
|
+
- **[`UpdateProfileParams`](#tip-tanımları)** - Profil güncelleme parametreleri
|
|
439
|
+
|
|
440
|
+
## İlgili Hook'lar
|
|
441
|
+
|
|
442
|
+
- **[`useUserProfile`](../../presentation/hooks/useUserProfile.md)** - Profil verileri hook'u
|
|
443
|
+
- **[`useProfileUpdate`](../../presentation/hooks/useProfileUpdate.md)** - Profil güncelleme hook'u
|