@umituz/react-native-auth 1.6.9 → 1.7.0
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 +54 -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
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useAuthState Hook
|
|
3
|
+
* Single Responsibility: Manage authentication state
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useEffect } from "react";
|
|
7
|
+
import type { User } from "firebase/auth";
|
|
8
|
+
import { DeviceEventEmitter } from "react-native";
|
|
9
|
+
import { getAuthService } from "../../infrastructure/services/AuthService";
|
|
10
|
+
import { useFirebaseAuth } from "@umituz/react-native-firebase-auth";
|
|
11
|
+
|
|
12
|
+
export interface UseAuthStateResult {
|
|
13
|
+
user: User | null;
|
|
14
|
+
isAuthenticated: boolean;
|
|
15
|
+
isGuest: boolean;
|
|
16
|
+
loading: boolean;
|
|
17
|
+
error: string | null;
|
|
18
|
+
setError: (error: string | null) => void;
|
|
19
|
+
setIsGuest: (isGuest: boolean) => void;
|
|
20
|
+
setLoading: (loading: boolean) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Hook for managing authentication state
|
|
25
|
+
*/
|
|
26
|
+
export function useAuthState(): UseAuthStateResult {
|
|
27
|
+
const { user: firebaseUser, loading: firebaseLoading } = useFirebaseAuth();
|
|
28
|
+
const [isGuest, setIsGuest] = useState(() => {
|
|
29
|
+
const service = getAuthService();
|
|
30
|
+
return service ? service.getIsGuestMode() : false;
|
|
31
|
+
});
|
|
32
|
+
const [error, setError] = useState<string | null>(null);
|
|
33
|
+
const [loading, setLoading] = useState(false);
|
|
34
|
+
|
|
35
|
+
const user = isGuest ? null : firebaseUser;
|
|
36
|
+
const isAuthenticated = !!user && !isGuest;
|
|
37
|
+
|
|
38
|
+
// Reset guest mode when user signs in
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (firebaseUser && isGuest) {
|
|
41
|
+
setIsGuest(false);
|
|
42
|
+
}
|
|
43
|
+
}, [firebaseUser, isGuest]);
|
|
44
|
+
|
|
45
|
+
// Sync isGuest state with service on mount
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const service = getAuthService();
|
|
48
|
+
if (service) {
|
|
49
|
+
const serviceIsGuest = service.getIsGuestMode();
|
|
50
|
+
if (serviceIsGuest !== isGuest) {
|
|
51
|
+
setIsGuest(serviceIsGuest);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}, []);
|
|
55
|
+
|
|
56
|
+
// Listen for guest-mode-enabled event
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const guestSubscription = DeviceEventEmitter.addListener(
|
|
59
|
+
"guest-mode-enabled",
|
|
60
|
+
() => {
|
|
61
|
+
/* eslint-disable-next-line no-console */
|
|
62
|
+
if (__DEV__) {
|
|
63
|
+
console.log("[useAuthState] Guest mode enabled event received");
|
|
64
|
+
}
|
|
65
|
+
setIsGuest(true);
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const authSubscription = DeviceEventEmitter.addListener(
|
|
70
|
+
"user-authenticated",
|
|
71
|
+
() => {
|
|
72
|
+
/* eslint-disable-next-line no-console */
|
|
73
|
+
if (__DEV__) {
|
|
74
|
+
console.log("[useAuthState] User authenticated event received");
|
|
75
|
+
}
|
|
76
|
+
if (isGuest) {
|
|
77
|
+
setIsGuest(false);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return () => {
|
|
83
|
+
guestSubscription.remove();
|
|
84
|
+
authSubscription.remove();
|
|
85
|
+
};
|
|
86
|
+
}, [isGuest]);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
user,
|
|
90
|
+
isAuthenticated,
|
|
91
|
+
isGuest,
|
|
92
|
+
loading: loading || firebaseLoading,
|
|
93
|
+
error,
|
|
94
|
+
setError,
|
|
95
|
+
setIsGuest,
|
|
96
|
+
setLoading,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useLoginForm Hook
|
|
3
|
+
* Single Responsibility: Handle login form logic
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback } from "react";
|
|
7
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
8
|
+
import { useAuth } from "./useAuth";
|
|
9
|
+
import { getAuthErrorLocalizationKey } from "../utils/getAuthErrorMessage";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validate email format
|
|
13
|
+
*/
|
|
14
|
+
function validateEmail(emailValue: string): boolean {
|
|
15
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
16
|
+
return emailRegex.test(emailValue);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UseLoginFormResult {
|
|
20
|
+
email: string;
|
|
21
|
+
password: string;
|
|
22
|
+
emailError: string | null;
|
|
23
|
+
passwordError: string | null;
|
|
24
|
+
localError: string | null;
|
|
25
|
+
loading: boolean;
|
|
26
|
+
handleEmailChange: (text: string) => void;
|
|
27
|
+
handlePasswordChange: (text: string) => void;
|
|
28
|
+
handleSignIn: () => Promise<void>;
|
|
29
|
+
handleContinueAsGuest: () => Promise<void>;
|
|
30
|
+
displayError: string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Hook for login form logic
|
|
35
|
+
*/
|
|
36
|
+
export function useLoginForm(): UseLoginFormResult {
|
|
37
|
+
const { t } = useLocalization();
|
|
38
|
+
const { signIn, loading, error, continueAsGuest } = useAuth();
|
|
39
|
+
|
|
40
|
+
const [email, setEmail] = useState("");
|
|
41
|
+
const [password, setPassword] = useState("");
|
|
42
|
+
const [emailError, setEmailError] = useState<string | null>(null);
|
|
43
|
+
const [passwordError, setPasswordError] = useState<string | null>(null);
|
|
44
|
+
const [localError, setLocalError] = useState<string | null>(null);
|
|
45
|
+
|
|
46
|
+
const handleEmailChange = useCallback((text: string) => {
|
|
47
|
+
setEmail(text);
|
|
48
|
+
if (emailError) setEmailError(null);
|
|
49
|
+
if (localError) setLocalError(null);
|
|
50
|
+
}, [emailError, localError]);
|
|
51
|
+
|
|
52
|
+
const handlePasswordChange = useCallback((text: string) => {
|
|
53
|
+
setPassword(text);
|
|
54
|
+
if (passwordError) setPasswordError(null);
|
|
55
|
+
if (localError) setLocalError(null);
|
|
56
|
+
}, [passwordError, localError]);
|
|
57
|
+
|
|
58
|
+
const handleSignIn = useCallback(async () => {
|
|
59
|
+
/* eslint-disable-next-line no-console */
|
|
60
|
+
if (__DEV__) {
|
|
61
|
+
console.log("[useLoginForm] handleSignIn called");
|
|
62
|
+
}
|
|
63
|
+
setEmailError(null);
|
|
64
|
+
setPasswordError(null);
|
|
65
|
+
setLocalError(null);
|
|
66
|
+
|
|
67
|
+
let hasError = false;
|
|
68
|
+
|
|
69
|
+
if (!email.trim()) {
|
|
70
|
+
setEmailError(t("auth.errors.invalidEmail"));
|
|
71
|
+
hasError = true;
|
|
72
|
+
} else if (!validateEmail(email.trim())) {
|
|
73
|
+
setEmailError(t("auth.errors.invalidEmail"));
|
|
74
|
+
hasError = true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!password.trim()) {
|
|
78
|
+
setPasswordError(t("auth.errors.weakPassword"));
|
|
79
|
+
hasError = true;
|
|
80
|
+
} else if (password.length < 6) {
|
|
81
|
+
setPasswordError(t("auth.errors.weakPassword"));
|
|
82
|
+
hasError = true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (hasError) {
|
|
86
|
+
/* eslint-disable-next-line no-console */
|
|
87
|
+
if (__DEV__) {
|
|
88
|
+
console.log("[useLoginForm] Validation errors, returning early");
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
/* eslint-disable-next-line no-console */
|
|
95
|
+
if (__DEV__) {
|
|
96
|
+
console.log("[useLoginForm] Calling signIn with email:", email.trim());
|
|
97
|
+
}
|
|
98
|
+
await signIn(email.trim(), password);
|
|
99
|
+
/* eslint-disable-next-line no-console */
|
|
100
|
+
if (__DEV__) {
|
|
101
|
+
console.log("[useLoginForm] signIn completed successfully");
|
|
102
|
+
}
|
|
103
|
+
} catch (err: any) {
|
|
104
|
+
/* eslint-disable-next-line no-console */
|
|
105
|
+
if (__DEV__) {
|
|
106
|
+
console.error("[useLoginForm] signIn error:", err);
|
|
107
|
+
}
|
|
108
|
+
const localizationKey = getAuthErrorLocalizationKey(err);
|
|
109
|
+
const errorMessage = t(localizationKey);
|
|
110
|
+
setLocalError(errorMessage);
|
|
111
|
+
}
|
|
112
|
+
}, [email, password, t, signIn]);
|
|
113
|
+
|
|
114
|
+
const handleContinueAsGuest = useCallback(async () => {
|
|
115
|
+
/* eslint-disable-next-line no-console */
|
|
116
|
+
if (__DEV__) {
|
|
117
|
+
console.log("========================================");
|
|
118
|
+
console.log("[useLoginForm] 🎯 Continue as Guest button PRESSED");
|
|
119
|
+
console.log("[useLoginForm] Current loading state:", loading);
|
|
120
|
+
console.log("[useLoginForm] Current error state:", error);
|
|
121
|
+
console.log("========================================");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
/* eslint-disable-next-line no-console */
|
|
126
|
+
if (__DEV__) {
|
|
127
|
+
console.log("[useLoginForm] Calling continueAsGuest() function...");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await continueAsGuest();
|
|
131
|
+
|
|
132
|
+
/* eslint-disable-next-line no-console */
|
|
133
|
+
if (__DEV__) {
|
|
134
|
+
console.log("[useLoginForm] ✅ continueAsGuest() completed successfully");
|
|
135
|
+
console.log("[useLoginForm] Current loading state after:", loading);
|
|
136
|
+
}
|
|
137
|
+
} catch (err) {
|
|
138
|
+
/* eslint-disable-next-line no-console */
|
|
139
|
+
if (__DEV__) {
|
|
140
|
+
console.error("[useLoginForm] ❌ ERROR in continueAsGuest:", err);
|
|
141
|
+
console.error("[useLoginForm] Error details:", {
|
|
142
|
+
message: err instanceof Error ? err.message : String(err),
|
|
143
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}, [continueAsGuest, loading, error]);
|
|
148
|
+
|
|
149
|
+
const displayError = localError || error;
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
email,
|
|
153
|
+
password,
|
|
154
|
+
emailError,
|
|
155
|
+
passwordError,
|
|
156
|
+
localError,
|
|
157
|
+
loading,
|
|
158
|
+
handleEmailChange,
|
|
159
|
+
handlePasswordChange,
|
|
160
|
+
handleSignIn,
|
|
161
|
+
handleContinueAsGuest,
|
|
162
|
+
displayError,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useRegisterForm Hook
|
|
3
|
+
* Single Responsibility: Handle register form logic
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback } from "react";
|
|
7
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
8
|
+
import {
|
|
9
|
+
validateEmail,
|
|
10
|
+
validatePassword,
|
|
11
|
+
validatePasswordConfirmation,
|
|
12
|
+
batchValidate,
|
|
13
|
+
} from "@umituz/react-native-validation";
|
|
14
|
+
import { useAuth } from "./useAuth";
|
|
15
|
+
import { getAuthErrorLocalizationKey } from "../utils/getAuthErrorMessage";
|
|
16
|
+
|
|
17
|
+
export interface UseRegisterFormResult {
|
|
18
|
+
displayName: string;
|
|
19
|
+
email: string;
|
|
20
|
+
password: string;
|
|
21
|
+
confirmPassword: string;
|
|
22
|
+
fieldErrors: {
|
|
23
|
+
displayName?: string;
|
|
24
|
+
email?: string;
|
|
25
|
+
password?: string;
|
|
26
|
+
confirmPassword?: string;
|
|
27
|
+
};
|
|
28
|
+
localError: string | null;
|
|
29
|
+
loading: boolean;
|
|
30
|
+
handleDisplayNameChange: (text: string) => void;
|
|
31
|
+
handleEmailChange: (text: string) => void;
|
|
32
|
+
handlePasswordChange: (text: string) => void;
|
|
33
|
+
handleConfirmPasswordChange: (text: string) => void;
|
|
34
|
+
handleSignUp: () => Promise<void>;
|
|
35
|
+
displayError: string | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Hook for register form logic
|
|
40
|
+
*/
|
|
41
|
+
export function useRegisterForm(): UseRegisterFormResult {
|
|
42
|
+
const { t } = useLocalization();
|
|
43
|
+
const { signUp, loading, error } = useAuth();
|
|
44
|
+
|
|
45
|
+
const [displayName, setDisplayName] = useState("");
|
|
46
|
+
const [email, setEmail] = useState("");
|
|
47
|
+
const [password, setPassword] = useState("");
|
|
48
|
+
const [confirmPassword, setConfirmPassword] = useState("");
|
|
49
|
+
const [localError, setLocalError] = useState<string | null>(null);
|
|
50
|
+
const [fieldErrors, setFieldErrors] = useState<{
|
|
51
|
+
displayName?: string;
|
|
52
|
+
email?: string;
|
|
53
|
+
password?: string;
|
|
54
|
+
confirmPassword?: string;
|
|
55
|
+
}>({});
|
|
56
|
+
|
|
57
|
+
const handleDisplayNameChange = useCallback((text: string) => {
|
|
58
|
+
setDisplayName(text);
|
|
59
|
+
setFieldErrors((prev) => {
|
|
60
|
+
const next = { ...prev };
|
|
61
|
+
if (next.displayName) {
|
|
62
|
+
delete next.displayName;
|
|
63
|
+
}
|
|
64
|
+
return next;
|
|
65
|
+
});
|
|
66
|
+
if (localError) setLocalError(null);
|
|
67
|
+
}, [localError]);
|
|
68
|
+
|
|
69
|
+
const handleEmailChange = useCallback((text: string) => {
|
|
70
|
+
setEmail(text);
|
|
71
|
+
setFieldErrors((prev) => {
|
|
72
|
+
const next = { ...prev };
|
|
73
|
+
if (next.email) {
|
|
74
|
+
delete next.email;
|
|
75
|
+
}
|
|
76
|
+
return next;
|
|
77
|
+
});
|
|
78
|
+
if (localError) setLocalError(null);
|
|
79
|
+
}, [localError]);
|
|
80
|
+
|
|
81
|
+
const handlePasswordChange = useCallback((text: string) => {
|
|
82
|
+
setPassword(text);
|
|
83
|
+
setFieldErrors((prev) => {
|
|
84
|
+
const next = { ...prev };
|
|
85
|
+
if (next.password) {
|
|
86
|
+
delete next.password;
|
|
87
|
+
}
|
|
88
|
+
if (next.confirmPassword) {
|
|
89
|
+
delete next.confirmPassword;
|
|
90
|
+
}
|
|
91
|
+
return next;
|
|
92
|
+
});
|
|
93
|
+
if (localError) setLocalError(null);
|
|
94
|
+
}, [localError]);
|
|
95
|
+
|
|
96
|
+
const handleConfirmPasswordChange = useCallback((text: string) => {
|
|
97
|
+
setConfirmPassword(text);
|
|
98
|
+
setFieldErrors((prev) => {
|
|
99
|
+
const next = { ...prev };
|
|
100
|
+
if (next.confirmPassword) {
|
|
101
|
+
delete next.confirmPassword;
|
|
102
|
+
}
|
|
103
|
+
return next;
|
|
104
|
+
});
|
|
105
|
+
if (localError) setLocalError(null);
|
|
106
|
+
}, [localError]);
|
|
107
|
+
|
|
108
|
+
const handleSignUp = useCallback(async () => {
|
|
109
|
+
setLocalError(null);
|
|
110
|
+
setFieldErrors({});
|
|
111
|
+
|
|
112
|
+
const validationResult = batchValidate([
|
|
113
|
+
{
|
|
114
|
+
field: "email",
|
|
115
|
+
validator: () => validateEmail(email.trim()),
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
field: "password",
|
|
119
|
+
validator: () =>
|
|
120
|
+
validatePassword(password, {
|
|
121
|
+
minLength: 6,
|
|
122
|
+
requireUppercase: false,
|
|
123
|
+
requireLowercase: false,
|
|
124
|
+
requireNumber: false,
|
|
125
|
+
}),
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
field: "confirmPassword",
|
|
129
|
+
validator: () =>
|
|
130
|
+
validatePasswordConfirmation(password, confirmPassword),
|
|
131
|
+
},
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
if (!validationResult.isValid) {
|
|
135
|
+
setFieldErrors(validationResult.errors);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
await signUp(
|
|
141
|
+
email.trim(),
|
|
142
|
+
password,
|
|
143
|
+
displayName.trim() || undefined,
|
|
144
|
+
);
|
|
145
|
+
} catch (err: any) {
|
|
146
|
+
const localizationKey = getAuthErrorLocalizationKey(err);
|
|
147
|
+
const errorMessage = t(localizationKey);
|
|
148
|
+
setLocalError(errorMessage);
|
|
149
|
+
}
|
|
150
|
+
}, [displayName, email, password, confirmPassword, signUp, t]);
|
|
151
|
+
|
|
152
|
+
const displayError = localError || error;
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
displayName,
|
|
156
|
+
email,
|
|
157
|
+
password,
|
|
158
|
+
confirmPassword,
|
|
159
|
+
fieldErrors,
|
|
160
|
+
localError,
|
|
161
|
+
loading,
|
|
162
|
+
handleDisplayNameChange,
|
|
163
|
+
handleEmailChange,
|
|
164
|
+
handlePasswordChange,
|
|
165
|
+
handleConfirmPasswordChange,
|
|
166
|
+
handleSignUp,
|
|
167
|
+
displayError,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|