@umituz/react-native-auth 1.6.9 → 1.7.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 +1 -1
- package/src/infrastructure/services/AuthService.ts +14 -173
- package/src/infrastructure/storage/GuestModeStorage.ts +55 -0
- package/src/infrastructure/utils/AuthErrorMapper.ts +57 -0
- package/src/infrastructure/utils/AuthEventEmitter.ts +29 -0
- package/src/infrastructure/utils/AuthValidation.ts +60 -0
- package/src/presentation/components/LoginForm.tsx +16 -130
- package/src/presentation/components/RegisterForm.tsx +17 -104
- package/src/presentation/hooks/useAuth.ts +14 -279
- package/src/presentation/hooks/useAuthActions.ts +186 -0
- package/src/presentation/hooks/useAuthState.ts +99 -0
- package/src/presentation/hooks/useLoginForm.ts +165 -0
- package/src/presentation/hooks/useRegisterForm.ts +170 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-auth",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design supports Firebase Auth and can be adapted for Supabase or other providers.",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -14,119 +14,17 @@ import {
|
|
|
14
14
|
} from "firebase/auth";
|
|
15
15
|
import type { IAuthService, SignUpParams, SignInParams } from "../../application/ports/IAuthService";
|
|
16
16
|
import {
|
|
17
|
-
AuthError,
|
|
18
17
|
AuthInitializationError,
|
|
19
|
-
AuthConfigurationError,
|
|
20
18
|
AuthValidationError,
|
|
21
19
|
AuthWeakPasswordError,
|
|
22
20
|
AuthInvalidEmailError,
|
|
23
|
-
AuthEmailAlreadyInUseError,
|
|
24
|
-
AuthWrongPasswordError,
|
|
25
|
-
AuthUserNotFoundError,
|
|
26
|
-
AuthNetworkError,
|
|
27
21
|
} from "../../domain/errors/AuthError";
|
|
28
22
|
import type { AuthConfig } from "../../domain/value-objects/AuthConfig";
|
|
29
23
|
import { DEFAULT_AUTH_CONFIG } from "../../domain/value-objects/AuthConfig";
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Validate email format
|
|
37
|
-
*/
|
|
38
|
-
function validateEmail(email: string): boolean {
|
|
39
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
40
|
-
return emailRegex.test(email.trim());
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Validate password strength
|
|
45
|
-
*/
|
|
46
|
-
function validatePassword(
|
|
47
|
-
password: string,
|
|
48
|
-
config: Required<Omit<AuthConfig, "onUserCreated" | "onUserUpdated" | "onSignOut">>
|
|
49
|
-
): { valid: boolean; error?: string } {
|
|
50
|
-
if (password.length < config.minPasswordLength) {
|
|
51
|
-
return {
|
|
52
|
-
valid: false,
|
|
53
|
-
error: `Password must be at least ${config.minPasswordLength} characters long`,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (config.requireUppercase && !/[A-Z]/.test(password)) {
|
|
58
|
-
return {
|
|
59
|
-
valid: false,
|
|
60
|
-
error: "Password must contain at least one uppercase letter",
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (config.requireLowercase && !/[a-z]/.test(password)) {
|
|
65
|
-
return {
|
|
66
|
-
valid: false,
|
|
67
|
-
error: "Password must contain at least one lowercase letter",
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (config.requireNumbers && !/[0-9]/.test(password)) {
|
|
72
|
-
return {
|
|
73
|
-
valid: false,
|
|
74
|
-
error: "Password must contain at least one number",
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (config.requireSpecialChars && !/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
|
|
79
|
-
return {
|
|
80
|
-
valid: false,
|
|
81
|
-
error: "Password must contain at least one special character",
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return { valid: true };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Map Firebase Auth errors to domain errors
|
|
90
|
-
*/
|
|
91
|
-
function mapFirebaseAuthError(error: any): Error {
|
|
92
|
-
// Extract error code and message
|
|
93
|
-
const code = error?.code || "";
|
|
94
|
-
const message = error?.message || "Authentication failed";
|
|
95
|
-
|
|
96
|
-
// Firebase Auth error codes
|
|
97
|
-
if (code === "auth/email-already-in-use") {
|
|
98
|
-
return new AuthEmailAlreadyInUseError();
|
|
99
|
-
}
|
|
100
|
-
if (code === "auth/invalid-email") {
|
|
101
|
-
return new AuthInvalidEmailError();
|
|
102
|
-
}
|
|
103
|
-
if (code === "auth/weak-password") {
|
|
104
|
-
return new AuthWeakPasswordError();
|
|
105
|
-
}
|
|
106
|
-
if (code === "auth/user-disabled") {
|
|
107
|
-
return new AuthError("User account has been disabled", "AUTH_USER_DISABLED");
|
|
108
|
-
}
|
|
109
|
-
if (code === "auth/user-not-found") {
|
|
110
|
-
return new AuthUserNotFoundError();
|
|
111
|
-
}
|
|
112
|
-
if (code === "auth/wrong-password") {
|
|
113
|
-
return new AuthWrongPasswordError();
|
|
114
|
-
}
|
|
115
|
-
if (code === "auth/network-request-failed") {
|
|
116
|
-
return new AuthNetworkError();
|
|
117
|
-
}
|
|
118
|
-
if (code === "auth/too-many-requests") {
|
|
119
|
-
return new AuthError("Too many requests. Please try again later.", "AUTH_TOO_MANY_REQUESTS");
|
|
120
|
-
}
|
|
121
|
-
if (code === "auth/configuration-not-found" || code === "auth/app-not-authorized") {
|
|
122
|
-
return new AuthConfigurationError("Authentication is not properly configured. Please contact support.");
|
|
123
|
-
}
|
|
124
|
-
if (code === "auth/operation-not-allowed") {
|
|
125
|
-
return new AuthConfigurationError("Email/password authentication is not enabled. Please contact support.");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return new AuthError(message, code);
|
|
129
|
-
}
|
|
24
|
+
import { validateEmail, validatePassword } from "../utils/AuthValidation";
|
|
25
|
+
import { mapFirebaseAuthError } from "../utils/AuthErrorMapper";
|
|
26
|
+
import { emitUserAuthenticated, emitGuestModeEnabled } from "../utils/AuthEventEmitter";
|
|
27
|
+
import { loadGuestMode, saveGuestMode, clearGuestMode } from "../storage/GuestModeStorage";
|
|
130
28
|
|
|
131
29
|
export class AuthService implements IAuthService {
|
|
132
30
|
private auth: Auth | null = null;
|
|
@@ -146,15 +44,7 @@ export class AuthService implements IAuthService {
|
|
|
146
44
|
throw new AuthInitializationError("Auth instance is required");
|
|
147
45
|
}
|
|
148
46
|
this.auth = auth;
|
|
149
|
-
|
|
150
|
-
// Restore guest mode from storage
|
|
151
|
-
try {
|
|
152
|
-
const guestModeValue = await storageRepository.getString(GUEST_MODE_KEY, "false");
|
|
153
|
-
this.isGuestMode = guestModeValue === "true";
|
|
154
|
-
} catch (error) {
|
|
155
|
-
// If storage fails, default to false
|
|
156
|
-
this.isGuestMode = false;
|
|
157
|
-
}
|
|
47
|
+
this.isGuestMode = await loadGuestMode();
|
|
158
48
|
}
|
|
159
49
|
|
|
160
50
|
/**
|
|
@@ -218,15 +108,10 @@ export class AuthService implements IAuthService {
|
|
|
218
108
|
// Clear guest mode when user signs up
|
|
219
109
|
if (this.isGuestMode) {
|
|
220
110
|
this.isGuestMode = false;
|
|
221
|
-
|
|
222
|
-
await storageRepository.removeItem(GUEST_MODE_KEY);
|
|
223
|
-
} catch (error) {
|
|
224
|
-
// Ignore storage errors
|
|
225
|
-
}
|
|
111
|
+
await clearGuestMode();
|
|
226
112
|
}
|
|
227
113
|
|
|
228
|
-
|
|
229
|
-
DeviceEventEmitter.emit("user-authenticated", { userId: userCredential.user.uid });
|
|
114
|
+
emitUserAuthenticated(userCredential.user.uid);
|
|
230
115
|
|
|
231
116
|
return userCredential.user;
|
|
232
117
|
} catch (error: any) {
|
|
@@ -276,23 +161,10 @@ export class AuthService implements IAuthService {
|
|
|
276
161
|
// Clear guest mode when user signs in
|
|
277
162
|
if (this.isGuestMode) {
|
|
278
163
|
this.isGuestMode = false;
|
|
279
|
-
|
|
280
|
-
await storageRepository.removeItem(GUEST_MODE_KEY);
|
|
281
|
-
/* eslint-disable-next-line no-console */
|
|
282
|
-
if (__DEV__) {
|
|
283
|
-
console.log("[AuthService] Guest mode cleared from storage");
|
|
284
|
-
}
|
|
285
|
-
} catch (error) {
|
|
286
|
-
// Ignore storage errors
|
|
287
|
-
}
|
|
164
|
+
await clearGuestMode();
|
|
288
165
|
}
|
|
289
166
|
|
|
290
|
-
|
|
291
|
-
/* eslint-disable-next-line no-console */
|
|
292
|
-
if (__DEV__) {
|
|
293
|
-
console.log("[AuthService] Emitting user-authenticated event");
|
|
294
|
-
}
|
|
295
|
-
DeviceEventEmitter.emit("user-authenticated", { userId: userCredential.user.uid });
|
|
167
|
+
emitUserAuthenticated(userCredential.user.uid);
|
|
296
168
|
|
|
297
169
|
return userCredential.user;
|
|
298
170
|
} catch (error: any) {
|
|
@@ -310,20 +182,13 @@ export class AuthService implements IAuthService {
|
|
|
310
182
|
async signOut(): Promise<void> {
|
|
311
183
|
const auth = this.getAuth();
|
|
312
184
|
if (!auth) {
|
|
313
|
-
// If auth is not initialized, just clear guest mode
|
|
314
185
|
this.isGuestMode = false;
|
|
315
|
-
|
|
316
|
-
await storageRepository.removeItem(GUEST_MODE_KEY);
|
|
317
|
-
} catch (error) {
|
|
318
|
-
// Ignore storage errors
|
|
319
|
-
}
|
|
186
|
+
await clearGuestMode();
|
|
320
187
|
return;
|
|
321
188
|
}
|
|
322
189
|
|
|
323
190
|
try {
|
|
324
191
|
await firebaseSignOut(auth);
|
|
325
|
-
// Don't clear guest mode on sign out - user might want to continue as guest
|
|
326
|
-
// Guest mode will be cleared when user signs in or signs up
|
|
327
192
|
} catch (error: any) {
|
|
328
193
|
throw mapFirebaseAuthError(error);
|
|
329
194
|
}
|
|
@@ -340,41 +205,17 @@ export class AuthService implements IAuthService {
|
|
|
340
205
|
const auth = this.getAuth();
|
|
341
206
|
|
|
342
207
|
// Sign out from Firebase if logged in
|
|
343
|
-
if (auth
|
|
208
|
+
if (auth?.currentUser) {
|
|
344
209
|
try {
|
|
345
210
|
await firebaseSignOut(auth);
|
|
346
|
-
} catch
|
|
211
|
+
} catch {
|
|
347
212
|
// Ignore sign out errors when switching to guest mode
|
|
348
213
|
}
|
|
349
214
|
}
|
|
350
215
|
|
|
351
216
|
this.isGuestMode = true;
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
console.log("[AuthService] isGuestMode set to true");
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Persist guest mode to storage
|
|
358
|
-
try {
|
|
359
|
-
await storageRepository.setString(GUEST_MODE_KEY, "true");
|
|
360
|
-
/* eslint-disable-next-line no-console */
|
|
361
|
-
if (__DEV__) {
|
|
362
|
-
console.log("[AuthService] Guest mode persisted to storage");
|
|
363
|
-
}
|
|
364
|
-
} catch (error) {
|
|
365
|
-
// Ignore storage errors - guest mode will still work in memory
|
|
366
|
-
/* eslint-disable-next-line no-console */
|
|
367
|
-
if (__DEV__) {
|
|
368
|
-
console.warn("[AuthService] Failed to persist guest mode to storage:", error);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Emit event for AppNavigator to handle navigation
|
|
373
|
-
/* eslint-disable-next-line no-console */
|
|
374
|
-
if (__DEV__) {
|
|
375
|
-
console.log("[AuthService] Emitting guest-mode-enabled event");
|
|
376
|
-
}
|
|
377
|
-
DeviceEventEmitter.emit("guest-mode-enabled");
|
|
217
|
+
await saveGuestMode(true);
|
|
218
|
+
emitGuestModeEnabled();
|
|
378
219
|
}
|
|
379
220
|
|
|
380
221
|
/**
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guest Mode Storage
|
|
3
|
+
* Single Responsibility: Manage guest mode persistence
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { storageRepository, unwrap } from "@umituz/react-native-storage";
|
|
7
|
+
|
|
8
|
+
const GUEST_MODE_KEY = "@auth_guest_mode";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Load guest mode from storage
|
|
12
|
+
*/
|
|
13
|
+
export async function loadGuestMode(): Promise<boolean> {
|
|
14
|
+
try {
|
|
15
|
+
const result = await storageRepository.getString(GUEST_MODE_KEY, "false");
|
|
16
|
+
const guestModeValue = unwrap(result, "false");
|
|
17
|
+
return guestModeValue === "true";
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Save guest mode to storage
|
|
25
|
+
*/
|
|
26
|
+
export async function saveGuestMode(isGuest: boolean): Promise<void> {
|
|
27
|
+
try {
|
|
28
|
+
if (isGuest) {
|
|
29
|
+
await storageRepository.setString(GUEST_MODE_KEY, "true");
|
|
30
|
+
/* eslint-disable-next-line no-console */
|
|
31
|
+
if (__DEV__) {
|
|
32
|
+
console.log("[GuestModeStorage] Guest mode persisted to storage");
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
await storageRepository.removeItem(GUEST_MODE_KEY);
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
/* eslint-disable-next-line no-console */
|
|
39
|
+
if (__DEV__) {
|
|
40
|
+
console.warn("[GuestModeStorage] Failed to persist guest mode:", error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Clear guest mode from storage
|
|
47
|
+
*/
|
|
48
|
+
export async function clearGuestMode(): Promise<void> {
|
|
49
|
+
try {
|
|
50
|
+
await storageRepository.removeItem(GUEST_MODE_KEY);
|
|
51
|
+
} catch {
|
|
52
|
+
// Ignore storage errors
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Error Mapper
|
|
3
|
+
* Single Responsibility: Map Firebase Auth errors to domain errors
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
AuthError,
|
|
8
|
+
AuthConfigurationError,
|
|
9
|
+
AuthEmailAlreadyInUseError,
|
|
10
|
+
AuthInvalidEmailError,
|
|
11
|
+
AuthWeakPasswordError,
|
|
12
|
+
AuthUserNotFoundError,
|
|
13
|
+
AuthWrongPasswordError,
|
|
14
|
+
AuthNetworkError,
|
|
15
|
+
} from "../../domain/errors/AuthError";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Map Firebase Auth errors to domain errors
|
|
19
|
+
*/
|
|
20
|
+
export function mapFirebaseAuthError(error: any): Error {
|
|
21
|
+
const code = error?.code || "";
|
|
22
|
+
const message = error?.message || "Authentication failed";
|
|
23
|
+
|
|
24
|
+
if (code === "auth/email-already-in-use") {
|
|
25
|
+
return new AuthEmailAlreadyInUseError();
|
|
26
|
+
}
|
|
27
|
+
if (code === "auth/invalid-email") {
|
|
28
|
+
return new AuthInvalidEmailError();
|
|
29
|
+
}
|
|
30
|
+
if (code === "auth/weak-password") {
|
|
31
|
+
return new AuthWeakPasswordError();
|
|
32
|
+
}
|
|
33
|
+
if (code === "auth/user-disabled") {
|
|
34
|
+
return new AuthError("User account has been disabled", "AUTH_USER_DISABLED");
|
|
35
|
+
}
|
|
36
|
+
if (code === "auth/user-not-found") {
|
|
37
|
+
return new AuthUserNotFoundError();
|
|
38
|
+
}
|
|
39
|
+
if (code === "auth/wrong-password") {
|
|
40
|
+
return new AuthWrongPasswordError();
|
|
41
|
+
}
|
|
42
|
+
if (code === "auth/network-request-failed") {
|
|
43
|
+
return new AuthNetworkError();
|
|
44
|
+
}
|
|
45
|
+
if (code === "auth/too-many-requests") {
|
|
46
|
+
return new AuthError("Too many requests. Please try again later.", "AUTH_TOO_MANY_REQUESTS");
|
|
47
|
+
}
|
|
48
|
+
if (code === "auth/configuration-not-found" || code === "auth/app-not-authorized") {
|
|
49
|
+
return new AuthConfigurationError("Authentication is not properly configured. Please contact support.");
|
|
50
|
+
}
|
|
51
|
+
if (code === "auth/operation-not-allowed") {
|
|
52
|
+
return new AuthConfigurationError("Email/password authentication is not enabled. Please contact support.");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return new AuthError(message, code);
|
|
56
|
+
}
|
|
57
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Event Emitter
|
|
3
|
+
* Single Responsibility: Emit auth-related events
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DeviceEventEmitter } from "react-native";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Emit user authenticated event
|
|
10
|
+
*/
|
|
11
|
+
export function emitUserAuthenticated(userId: string): void {
|
|
12
|
+
/* eslint-disable-next-line no-console */
|
|
13
|
+
if (__DEV__) {
|
|
14
|
+
console.log("[AuthEventEmitter] Emitting user-authenticated event");
|
|
15
|
+
}
|
|
16
|
+
DeviceEventEmitter.emit("user-authenticated", { userId });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Emit guest mode enabled event
|
|
21
|
+
*/
|
|
22
|
+
export function emitGuestModeEnabled(): void {
|
|
23
|
+
/* eslint-disable-next-line no-console */
|
|
24
|
+
if (__DEV__) {
|
|
25
|
+
console.log("[AuthEventEmitter] Emitting guest-mode-enabled event");
|
|
26
|
+
}
|
|
27
|
+
DeviceEventEmitter.emit("guest-mode-enabled");
|
|
28
|
+
}
|
|
29
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Validation Utilities
|
|
3
|
+
* Single Responsibility: Email and password validation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { AuthConfig } from "../../domain/value-objects/AuthConfig";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Validate email format
|
|
10
|
+
*/
|
|
11
|
+
export function validateEmail(email: string): boolean {
|
|
12
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
13
|
+
return emailRegex.test(email.trim());
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Validate password strength
|
|
18
|
+
*/
|
|
19
|
+
export function validatePassword(
|
|
20
|
+
password: string,
|
|
21
|
+
config: Required<Omit<AuthConfig, "onUserCreated" | "onUserUpdated" | "onSignOut">>
|
|
22
|
+
): { valid: boolean; error?: string } {
|
|
23
|
+
if (password.length < config.minPasswordLength) {
|
|
24
|
+
return {
|
|
25
|
+
valid: false,
|
|
26
|
+
error: `Password must be at least ${config.minPasswordLength} characters long`,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (config.requireUppercase && !/[A-Z]/.test(password)) {
|
|
31
|
+
return {
|
|
32
|
+
valid: false,
|
|
33
|
+
error: "Password must contain at least one uppercase letter",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (config.requireLowercase && !/[a-z]/.test(password)) {
|
|
38
|
+
return {
|
|
39
|
+
valid: false,
|
|
40
|
+
error: "Password must contain at least one lowercase letter",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (config.requireNumbers && !/[0-9]/.test(password)) {
|
|
45
|
+
return {
|
|
46
|
+
valid: false,
|
|
47
|
+
error: "Password must contain at least one number",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (config.requireSpecialChars && !/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
|
|
52
|
+
return {
|
|
53
|
+
valid: false,
|
|
54
|
+
error: "Password must contain at least one special character",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { valid: true };
|
|
59
|
+
}
|
|
60
|
+
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Login Form Component
|
|
3
|
-
*
|
|
3
|
+
* Single Responsibility: Render login form UI
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React
|
|
6
|
+
import React from "react";
|
|
7
7
|
import { View, StyleSheet } from "react-native";
|
|
8
8
|
import { AtomicInput, AtomicButton } from "@umituz/react-native-design-system-atoms";
|
|
9
9
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
10
|
-
import {
|
|
10
|
+
import { useLoginForm } from "../hooks/useLoginForm";
|
|
11
11
|
import { AuthErrorDisplay } from "./AuthErrorDisplay";
|
|
12
12
|
import { AuthDivider } from "./AuthDivider";
|
|
13
13
|
import { AuthLink } from "./AuthLink";
|
|
14
|
-
import { getAuthErrorLocalizationKey } from "../utils/getAuthErrorMessage";
|
|
15
14
|
|
|
16
15
|
interface LoginFormProps {
|
|
17
16
|
onNavigateToRegister: () => void;
|
|
@@ -21,124 +20,18 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
|
|
21
20
|
onNavigateToRegister,
|
|
22
21
|
}) => {
|
|
23
22
|
const { t } = useLocalization();
|
|
24
|
-
const {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const handleEmailChange = (text: string) => {
|
|
38
|
-
setEmail(text);
|
|
39
|
-
if (emailError) setEmailError(null);
|
|
40
|
-
if (localError) setLocalError(null);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const handlePasswordChange = (text: string) => {
|
|
44
|
-
setPassword(text);
|
|
45
|
-
if (passwordError) setPasswordError(null);
|
|
46
|
-
if (localError) setLocalError(null);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const handleSignIn = async () => {
|
|
50
|
-
/* eslint-disable-next-line no-console */
|
|
51
|
-
if (__DEV__) {
|
|
52
|
-
console.log("[LoginForm] handleSignIn called");
|
|
53
|
-
}
|
|
54
|
-
setEmailError(null);
|
|
55
|
-
setPasswordError(null);
|
|
56
|
-
setLocalError(null);
|
|
57
|
-
|
|
58
|
-
let hasError = false;
|
|
59
|
-
|
|
60
|
-
if (!email.trim()) {
|
|
61
|
-
setEmailError(t("auth.errors.invalidEmail"));
|
|
62
|
-
hasError = true;
|
|
63
|
-
} else if (!validateEmail(email.trim())) {
|
|
64
|
-
setEmailError(t("auth.errors.invalidEmail"));
|
|
65
|
-
hasError = true;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (!password.trim()) {
|
|
69
|
-
setPasswordError(t("auth.errors.weakPassword"));
|
|
70
|
-
hasError = true;
|
|
71
|
-
} else if (password.length < 6) {
|
|
72
|
-
setPasswordError(t("auth.errors.weakPassword"));
|
|
73
|
-
hasError = true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (hasError) {
|
|
77
|
-
/* eslint-disable-next-line no-console */
|
|
78
|
-
if (__DEV__) {
|
|
79
|
-
console.log("[LoginForm] Validation errors, returning early");
|
|
80
|
-
}
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
/* eslint-disable-next-line no-console */
|
|
86
|
-
if (__DEV__) {
|
|
87
|
-
console.log("[LoginForm] Calling signIn with email:", email.trim());
|
|
88
|
-
}
|
|
89
|
-
await signIn(email.trim(), password);
|
|
90
|
-
/* eslint-disable-next-line no-console */
|
|
91
|
-
if (__DEV__) {
|
|
92
|
-
console.log("[LoginForm] signIn completed successfully");
|
|
93
|
-
}
|
|
94
|
-
} catch (err: any) {
|
|
95
|
-
/* eslint-disable-next-line no-console */
|
|
96
|
-
if (__DEV__) {
|
|
97
|
-
console.error("[LoginForm] signIn error:", err);
|
|
98
|
-
}
|
|
99
|
-
const localizationKey = getAuthErrorLocalizationKey(err);
|
|
100
|
-
const errorMessage = t(localizationKey);
|
|
101
|
-
setLocalError(errorMessage);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const handleContinueAsGuest = async () => {
|
|
106
|
-
/* eslint-disable-next-line no-console */
|
|
107
|
-
if (__DEV__) {
|
|
108
|
-
console.log("========================================");
|
|
109
|
-
console.log("[LoginForm] 🎯 Continue as Guest button PRESSED");
|
|
110
|
-
console.log("[LoginForm] Current loading state:", loading);
|
|
111
|
-
console.log("[LoginForm] Current error state:", error);
|
|
112
|
-
console.log("========================================");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
/* eslint-disable-next-line no-console */
|
|
117
|
-
if (__DEV__) {
|
|
118
|
-
console.log("[LoginForm] Calling continueAsGuest() function...");
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
await continueAsGuest();
|
|
122
|
-
|
|
123
|
-
/* eslint-disable-next-line no-console */
|
|
124
|
-
if (__DEV__) {
|
|
125
|
-
console.log("[LoginForm] ✅ continueAsGuest() completed successfully");
|
|
126
|
-
console.log("[LoginForm] Current loading state after:", loading);
|
|
127
|
-
}
|
|
128
|
-
} catch (err) {
|
|
129
|
-
/* eslint-disable-next-line no-console */
|
|
130
|
-
if (__DEV__) {
|
|
131
|
-
console.error("[LoginForm] ❌ ERROR in continueAsGuest:", err);
|
|
132
|
-
console.error("[LoginForm] Error details:", {
|
|
133
|
-
message: err instanceof Error ? err.message : String(err),
|
|
134
|
-
stack: err instanceof Error ? err.stack : undefined,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
// Error handling is done in the hook
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const displayError = localError || error;
|
|
23
|
+
const {
|
|
24
|
+
email,
|
|
25
|
+
password,
|
|
26
|
+
emailError,
|
|
27
|
+
passwordError,
|
|
28
|
+
loading,
|
|
29
|
+
handleEmailChange,
|
|
30
|
+
handlePasswordChange,
|
|
31
|
+
handleSignIn,
|
|
32
|
+
handleContinueAsGuest,
|
|
33
|
+
displayError,
|
|
34
|
+
} = useLoginForm();
|
|
142
35
|
|
|
143
36
|
return (
|
|
144
37
|
<>
|
|
@@ -189,14 +82,7 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
|
|
189
82
|
<View style={styles.buttonContainer}>
|
|
190
83
|
<AtomicButton
|
|
191
84
|
variant="outline"
|
|
192
|
-
onPress={
|
|
193
|
-
/* eslint-disable-next-line no-console */
|
|
194
|
-
if (__DEV__) {
|
|
195
|
-
console.log("[LoginForm] 🔘 AtomicButton onPress triggered for Continue as Guest");
|
|
196
|
-
console.log("[LoginForm] Button disabled state:", loading);
|
|
197
|
-
}
|
|
198
|
-
handleContinueAsGuest();
|
|
199
|
-
}}
|
|
85
|
+
onPress={handleContinueAsGuest}
|
|
200
86
|
disabled={loading}
|
|
201
87
|
fullWidth
|
|
202
88
|
style={styles.guestButton}
|