@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.
@@ -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