@umituz/react-native-auth 4.3.54 → 4.3.55
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/AuthEventService.ts +5 -2
- package/src/infrastructure/services/initializeAuth.ts +6 -1
- package/src/infrastructure/utils/listener/anonymousHandler.ts +1 -3
- package/src/infrastructure/utils/listener/anonymousSignInHandler.ts +11 -4
- package/src/infrastructure/utils/listener/authListenerStateHandler.ts +10 -7
- package/src/infrastructure/utils/listener/initializationHandlers.ts +1 -3
- package/src/infrastructure/utils/listener/setupListener.ts +6 -4
- package/src/init/createAuthInitModule.ts +5 -1
- package/src/presentation/hooks/registerForm/registerFormSubmit.ts +7 -3
- package/src/presentation/hooks/useAuth.ts +23 -18
- package/src/presentation/hooks/useLoginForm.ts +8 -9
- package/src/presentation/stores/initializeAuthListener.ts +5 -1
- package/src/presentation/utils/form/validation/formValidators.ts +16 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-auth",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.55",
|
|
4
4
|
"description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design with dependency injection, configurable validation, and comprehensive error handling.",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -89,13 +89,16 @@ class AuthEventService {
|
|
|
89
89
|
private notifyListeners(event: string, payload: AuthEventPayload): void {
|
|
90
90
|
const eventListeners = this.listeners.get(event);
|
|
91
91
|
if (eventListeners) {
|
|
92
|
-
|
|
92
|
+
// Copy array before iterating to prevent issues if a listener
|
|
93
|
+
// removes itself or other listeners during notification
|
|
94
|
+
const snapshot = [...eventListeners];
|
|
95
|
+
for (const listener of snapshot) {
|
|
93
96
|
try {
|
|
94
97
|
listener(payload);
|
|
95
98
|
} catch (error) {
|
|
96
99
|
console.error(`[AuthEventService] Listener error for "${event}":`, error);
|
|
97
100
|
}
|
|
98
|
-
}
|
|
101
|
+
}
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
}
|
|
@@ -27,6 +27,7 @@ export interface InitializeAuthOptions {
|
|
|
27
27
|
|
|
28
28
|
let isInitialized = false;
|
|
29
29
|
let initializationPromise: Promise<{ success: boolean }> | null = null;
|
|
30
|
+
let listenerUnsubscribe: (() => void) | null = null;
|
|
30
31
|
const conversionState: { current: ConversionState } = {
|
|
31
32
|
current: { previousUserId: null, wasAnonymous: false },
|
|
32
33
|
};
|
|
@@ -102,7 +103,7 @@ async function doInitializeAuth(
|
|
|
102
103
|
onAuthStateChange,
|
|
103
104
|
});
|
|
104
105
|
|
|
105
|
-
initializeAuthListener({
|
|
106
|
+
listenerUnsubscribe = initializeAuthListener({
|
|
106
107
|
autoAnonymousSignIn,
|
|
107
108
|
onAuthStateChange: (user) => {
|
|
108
109
|
void handleAuthStateChange(user);
|
|
@@ -118,6 +119,10 @@ export function isAuthInitialized(): boolean {
|
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
export function resetAuthInitialization(): void {
|
|
122
|
+
if (listenerUnsubscribe) {
|
|
123
|
+
listenerUnsubscribe();
|
|
124
|
+
listenerUnsubscribe = null;
|
|
125
|
+
}
|
|
121
126
|
isInitialized = false;
|
|
122
127
|
conversionState.current = { previousUserId: null, wasAnonymous: false };
|
|
123
128
|
}
|
|
@@ -11,12 +11,10 @@ import {
|
|
|
11
11
|
completeAnonymousSignIn,
|
|
12
12
|
} from "./listenerState.util";
|
|
13
13
|
|
|
14
|
-
type Store = AuthActions & { isAnonymous: boolean };
|
|
15
|
-
|
|
16
14
|
/**
|
|
17
15
|
* Handle anonymous mode sign-in
|
|
18
16
|
*/
|
|
19
|
-
export async function handleAnonymousMode(store:
|
|
17
|
+
export async function handleAnonymousMode(store: AuthActions, auth: Auth): Promise<void> {
|
|
20
18
|
if (!startAnonymousSignIn()) {
|
|
21
19
|
return; // Already signing in
|
|
22
20
|
}
|
|
@@ -42,11 +42,13 @@ async function attemptAnonymousSignIn(
|
|
|
42
42
|
|
|
43
43
|
callbacks.onSignInStart();
|
|
44
44
|
|
|
45
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
46
|
+
|
|
45
47
|
try {
|
|
46
|
-
// Add timeout protection
|
|
47
|
-
const timeoutPromise = new Promise<never>((_, reject) =>
|
|
48
|
-
setTimeout(() => reject(new Error("Anonymous sign-in timeout")), timeout)
|
|
49
|
-
);
|
|
48
|
+
// Add timeout protection with proper cleanup
|
|
49
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
50
|
+
timeoutId = setTimeout(() => reject(new Error("Anonymous sign-in timeout")), timeout);
|
|
51
|
+
});
|
|
50
52
|
|
|
51
53
|
// Race between sign-in and timeout
|
|
52
54
|
await Promise.race([
|
|
@@ -58,6 +60,11 @@ async function attemptAnonymousSignIn(
|
|
|
58
60
|
} catch (error) {
|
|
59
61
|
const signInError = error instanceof Error ? error : new Error("Unknown sign-in error");
|
|
60
62
|
callbacks.onSignInFailure(signInError);
|
|
63
|
+
} finally {
|
|
64
|
+
// Always clear timeout to prevent timer leaks
|
|
65
|
+
if (timeoutId !== undefined) {
|
|
66
|
+
clearTimeout(timeoutId);
|
|
67
|
+
}
|
|
61
68
|
}
|
|
62
69
|
}
|
|
63
70
|
|
|
@@ -5,34 +5,37 @@
|
|
|
5
5
|
|
|
6
6
|
import type { Auth, User } from "firebase/auth";
|
|
7
7
|
import type { AuthActions } from "../../../types/auth-store.types";
|
|
8
|
-
import { completeInitialization } from "./listenerState.util";
|
|
9
8
|
import { handleAnonymousMode } from "./anonymousHandler";
|
|
10
9
|
import { safeCallbackSync } from "../safeCallback";
|
|
11
10
|
|
|
12
|
-
type
|
|
11
|
+
type StoreActions = AuthActions;
|
|
12
|
+
type GetIsAnonymous = () => boolean;
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Handle auth state change from Firebase
|
|
16
16
|
*/
|
|
17
17
|
export function handleAuthStateChange(
|
|
18
18
|
user: User | null,
|
|
19
|
-
store:
|
|
19
|
+
store: StoreActions,
|
|
20
20
|
auth: Auth,
|
|
21
21
|
autoAnonymousSignIn: boolean,
|
|
22
|
-
onAuthStateChange?: (user: User | null) => void | Promise<void
|
|
22
|
+
onAuthStateChange?: (user: User | null) => void | Promise<void>,
|
|
23
|
+
getIsAnonymous?: GetIsAnonymous
|
|
23
24
|
): void {
|
|
24
25
|
try {
|
|
25
26
|
if (!user && autoAnonymousSignIn) {
|
|
27
|
+
// Don't call completeInitialization here - handleAnonymousMode
|
|
28
|
+
// will set initialized/loading when anonymous sign-in completes or fails.
|
|
26
29
|
void handleAnonymousMode(store, auth);
|
|
27
|
-
completeInitialization();
|
|
28
30
|
return;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
store.setFirebaseUser(user);
|
|
32
34
|
store.setInitialized(true);
|
|
33
35
|
|
|
34
|
-
// Handle conversion from anonymous
|
|
35
|
-
|
|
36
|
+
// Handle conversion from anonymous - read fresh state, not stale snapshot
|
|
37
|
+
const currentIsAnonymous = getIsAnonymous ? getIsAnonymous() : false;
|
|
38
|
+
if (user && !user.isAnonymous && currentIsAnonymous) {
|
|
36
39
|
store.setIsAnonymous(false);
|
|
37
40
|
}
|
|
38
41
|
|
|
@@ -6,12 +6,10 @@
|
|
|
6
6
|
import type { AuthActions } from "../../../types/auth-store.types";
|
|
7
7
|
import { completeInitialization } from "./listenerState.util";
|
|
8
8
|
|
|
9
|
-
type Store = AuthActions & { isAnonymous: boolean };
|
|
10
|
-
|
|
11
9
|
/**
|
|
12
10
|
* Handle case where Firebase auth is not available
|
|
13
11
|
*/
|
|
14
|
-
export function handleNoFirebaseAuth(store:
|
|
12
|
+
export function handleNoFirebaseAuth(store: AuthActions): () => void {
|
|
15
13
|
completeInitialization();
|
|
16
14
|
store.setLoading(false);
|
|
17
15
|
store.setInitialized(true);
|
|
@@ -10,16 +10,18 @@ import { getAuthService } from "../../services/AuthService";
|
|
|
10
10
|
import { completeInitialization, setUnsubscribe } from "./listenerState.util";
|
|
11
11
|
import { handleAuthStateChange } from "./authListenerStateHandler";
|
|
12
12
|
|
|
13
|
-
type
|
|
13
|
+
type StoreActions = AuthActions;
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Setup Firebase auth listener with timeout protection
|
|
17
|
+
* @param getIsAnonymous - Function to read fresh isAnonymous state (avoids stale snapshots)
|
|
17
18
|
*/
|
|
18
19
|
export function setupAuthListener(
|
|
19
20
|
auth: Auth,
|
|
20
|
-
store:
|
|
21
|
+
store: StoreActions,
|
|
21
22
|
autoAnonymousSignIn: boolean,
|
|
22
|
-
onAuthStateChange?: (user: User | null) => void | Promise<void
|
|
23
|
+
onAuthStateChange?: (user: User | null) => void | Promise<void>,
|
|
24
|
+
getIsAnonymous?: () => boolean
|
|
23
25
|
): void {
|
|
24
26
|
const service = getAuthService();
|
|
25
27
|
|
|
@@ -46,7 +48,7 @@ export function setupAuthListener(
|
|
|
46
48
|
hasTriggered = true;
|
|
47
49
|
clearTimeout(timeout);
|
|
48
50
|
}
|
|
49
|
-
handleAuthStateChange(user, store, auth, autoAnonymousSignIn, onAuthStateChange);
|
|
51
|
+
handleAuthStateChange(user, store, auth, autoAnonymousSignIn, onAuthStateChange, getIsAnonymous);
|
|
50
52
|
});
|
|
51
53
|
|
|
52
54
|
setUnsubscribe(unsubscribe);
|
|
@@ -98,7 +98,11 @@ export function createAuthInitModule(
|
|
|
98
98
|
|
|
99
99
|
// Call custom callback if provided
|
|
100
100
|
if (onUserConverted) {
|
|
101
|
-
|
|
101
|
+
try {
|
|
102
|
+
await onUserConverted(anonymousId, authenticatedId);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('[AuthInitModule] onUserConverted callback failed:', error instanceof Error ? error.message : String(error));
|
|
105
|
+
}
|
|
102
106
|
}
|
|
103
107
|
},
|
|
104
108
|
});
|
|
@@ -33,10 +33,14 @@ export function useRegisterFormSubmit(
|
|
|
33
33
|
const handleSignUp = useCallback(async () => {
|
|
34
34
|
clearFormErrors();
|
|
35
35
|
|
|
36
|
+
// Sanitize once, use for both validation and sign-up
|
|
37
|
+
const sanitizedEmail = sanitizeEmail(fields.email);
|
|
38
|
+
const sanitizedName = sanitizeName(fields.displayName) || undefined;
|
|
39
|
+
|
|
36
40
|
const validation = validateRegisterForm(
|
|
37
41
|
{
|
|
38
|
-
displayName:
|
|
39
|
-
email:
|
|
42
|
+
displayName: sanitizedName,
|
|
43
|
+
email: sanitizedEmail,
|
|
40
44
|
password: fields.password,
|
|
41
45
|
confirmPassword: fields.confirmPassword,
|
|
42
46
|
},
|
|
@@ -50,7 +54,7 @@ export function useRegisterFormSubmit(
|
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
try {
|
|
53
|
-
await signUp(
|
|
57
|
+
await signUp(sanitizedEmail, fields.password, sanitizedName);
|
|
54
58
|
|
|
55
59
|
if (translations) {
|
|
56
60
|
alertService.success(translations.successTitle, translations.signUpSuccess);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* React hook for authentication state management
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { useCallback } from "react";
|
|
6
|
+
import { useCallback, useRef } from "react";
|
|
7
7
|
import { useAuthStore } from "../stores/authStore";
|
|
8
8
|
import {
|
|
9
9
|
selectUser,
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
selectError,
|
|
12
12
|
selectSetLoading,
|
|
13
13
|
selectSetError,
|
|
14
|
-
selectSetIsAnonymous,
|
|
15
14
|
selectIsAuthenticated,
|
|
16
15
|
selectHasFirebaseUser,
|
|
17
16
|
selectUserId,
|
|
@@ -57,21 +56,30 @@ export function useAuth(): UseAuthResult {
|
|
|
57
56
|
const isAuthReady = useAuthStore(selectIsAuthReady);
|
|
58
57
|
const setLoading = useAuthStore(selectSetLoading);
|
|
59
58
|
const setError = useAuthStore(selectSetError);
|
|
60
|
-
const setIsAnonymous = useAuthStore(selectSetIsAnonymous);
|
|
61
59
|
|
|
62
60
|
const signInMutation = useSignInMutation();
|
|
63
61
|
const signUpMutation = useSignUpMutation();
|
|
64
62
|
const signOutMutation = useSignOutMutation();
|
|
65
63
|
const anonymousModeMutation = useAnonymousModeMutation();
|
|
66
64
|
|
|
65
|
+
// Store mutateAsync in refs to avoid recreating callbacks on every render.
|
|
66
|
+
// useMutation returns a new object each render, but mutateAsync is stable.
|
|
67
|
+
const signUpMutateRef = useRef(signUpMutation.mutateAsync);
|
|
68
|
+
signUpMutateRef.current = signUpMutation.mutateAsync;
|
|
69
|
+
const signInMutateRef = useRef(signInMutation.mutateAsync);
|
|
70
|
+
signInMutateRef.current = signInMutation.mutateAsync;
|
|
71
|
+
const signOutMutateRef = useRef(signOutMutation.mutateAsync);
|
|
72
|
+
signOutMutateRef.current = signOutMutation.mutateAsync;
|
|
73
|
+
const anonymousMutateRef = useRef(anonymousModeMutation.mutateAsync);
|
|
74
|
+
anonymousMutateRef.current = anonymousModeMutation.mutateAsync;
|
|
75
|
+
|
|
67
76
|
const signUp = useCallback(
|
|
68
77
|
async (email: string, password: string, displayName?: string) => {
|
|
69
78
|
try {
|
|
70
79
|
setLoading(true);
|
|
71
80
|
setError(null);
|
|
72
|
-
await
|
|
73
|
-
//
|
|
74
|
-
setIsAnonymous(false);
|
|
81
|
+
await signUpMutateRef.current({ email, password, displayName });
|
|
82
|
+
// isAnonymous is automatically derived from firebaseUser by the auth listener
|
|
75
83
|
} catch (err: unknown) {
|
|
76
84
|
setError(err instanceof Error ? err.message : "Sign up failed");
|
|
77
85
|
throw err;
|
|
@@ -79,7 +87,7 @@ export function useAuth(): UseAuthResult {
|
|
|
79
87
|
setLoading(false);
|
|
80
88
|
}
|
|
81
89
|
},
|
|
82
|
-
[
|
|
90
|
+
[setLoading, setError]
|
|
83
91
|
);
|
|
84
92
|
|
|
85
93
|
const signIn = useCallback(
|
|
@@ -87,9 +95,8 @@ export function useAuth(): UseAuthResult {
|
|
|
87
95
|
try {
|
|
88
96
|
setLoading(true);
|
|
89
97
|
setError(null);
|
|
90
|
-
await
|
|
91
|
-
//
|
|
92
|
-
setIsAnonymous(false);
|
|
98
|
+
await signInMutateRef.current({ email, password });
|
|
99
|
+
// isAnonymous is automatically derived from firebaseUser by the auth listener
|
|
93
100
|
} catch (err: unknown) {
|
|
94
101
|
setError(err instanceof Error ? err.message : "Sign in failed");
|
|
95
102
|
throw err;
|
|
@@ -97,37 +104,35 @@ export function useAuth(): UseAuthResult {
|
|
|
97
104
|
setLoading(false);
|
|
98
105
|
}
|
|
99
106
|
},
|
|
100
|
-
[
|
|
107
|
+
[setLoading, setError]
|
|
101
108
|
);
|
|
102
109
|
|
|
103
110
|
const signOut = useCallback(async () => {
|
|
104
111
|
try {
|
|
105
112
|
setLoading(true);
|
|
106
113
|
setError(null);
|
|
107
|
-
await
|
|
114
|
+
await signOutMutateRef.current();
|
|
108
115
|
} catch (err: unknown) {
|
|
109
116
|
setError(err instanceof Error ? err.message : "Sign out failed");
|
|
110
117
|
throw err;
|
|
111
118
|
} finally {
|
|
112
119
|
setLoading(false);
|
|
113
120
|
}
|
|
114
|
-
}, [setLoading, setError
|
|
121
|
+
}, [setLoading, setError]);
|
|
115
122
|
|
|
116
123
|
const continueAnonymously = useCallback(async () => {
|
|
117
124
|
try {
|
|
118
125
|
setLoading(true);
|
|
119
126
|
setError(null);
|
|
120
|
-
await
|
|
121
|
-
//
|
|
122
|
-
setIsAnonymous(true);
|
|
127
|
+
await anonymousMutateRef.current();
|
|
128
|
+
// isAnonymous is automatically derived from firebaseUser by the auth listener
|
|
123
129
|
} catch (err: unknown) {
|
|
124
|
-
// Don't set anonymous flag on error - let user try again or choose another option
|
|
125
130
|
setError(err instanceof Error ? err.message : "Failed to continue anonymously");
|
|
126
131
|
throw err;
|
|
127
132
|
} finally {
|
|
128
133
|
setLoading(false);
|
|
129
134
|
}
|
|
130
|
-
}, [
|
|
135
|
+
}, [setLoading, setError]);
|
|
131
136
|
|
|
132
137
|
return {
|
|
133
138
|
user, userId, userType, loading, isAuthReady, isAnonymous, isAuthenticated, hasFirebaseUser, error,
|
|
@@ -44,17 +44,13 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
|
|
|
44
44
|
setEmailError(null);
|
|
45
45
|
setPasswordError(null);
|
|
46
46
|
setLocalError(null);
|
|
47
|
-
}, []);
|
|
47
|
+
}, [setLocalError]);
|
|
48
48
|
|
|
49
49
|
const { fields, updateField } = useFormFields(
|
|
50
50
|
{ email: "", password: "" },
|
|
51
51
|
{ clearLocalError }
|
|
52
52
|
);
|
|
53
53
|
|
|
54
|
-
const clearErrors = useCallback(() => {
|
|
55
|
-
clearFieldErrorsState();
|
|
56
|
-
}, [clearFieldErrorsState]);
|
|
57
|
-
|
|
58
54
|
const handleEmailChange = useCallback(
|
|
59
55
|
(text: string) => {
|
|
60
56
|
updateField("email", text);
|
|
@@ -72,10 +68,13 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
|
|
|
72
68
|
);
|
|
73
69
|
|
|
74
70
|
const handleSignIn = useCallback(async () => {
|
|
75
|
-
|
|
71
|
+
clearFieldErrorsState();
|
|
72
|
+
|
|
73
|
+
// Sanitize once, use for both validation and sign-in
|
|
74
|
+
const sanitizedEmail = sanitizeEmail(fields.email);
|
|
76
75
|
|
|
77
76
|
const validation = validateLoginForm(
|
|
78
|
-
{ email:
|
|
77
|
+
{ email: sanitizedEmail, password: fields.password },
|
|
79
78
|
getErrorMessage
|
|
80
79
|
);
|
|
81
80
|
|
|
@@ -88,7 +87,7 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
|
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
try {
|
|
91
|
-
await signIn(
|
|
90
|
+
await signIn(sanitizedEmail, fields.password);
|
|
92
91
|
|
|
93
92
|
if (translations) {
|
|
94
93
|
alertService.success(
|
|
@@ -99,7 +98,7 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
|
|
|
99
98
|
} catch (err: unknown) {
|
|
100
99
|
setLocalError(handleAuthError(err));
|
|
101
100
|
}
|
|
102
|
-
}, [fields, signIn, translations, handleAuthError, getErrorMessage,
|
|
101
|
+
}, [fields, signIn, translations, handleAuthError, getErrorMessage, clearFieldErrorsState, setLocalError]);
|
|
103
102
|
|
|
104
103
|
const handleContinueAnonymously = useCallback(async () => {
|
|
105
104
|
try {
|
|
@@ -48,8 +48,12 @@ export function initializeAuthListener(
|
|
|
48
48
|
return handleNoFirebaseAuth(store);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// Pass a getter function for isAnonymous to avoid stale snapshot reads.
|
|
52
|
+
// store.getState() returns a snapshot, but isAnonymous can change over time.
|
|
53
|
+
const getIsAnonymous = () => useAuthStore.getState().isAnonymous;
|
|
54
|
+
|
|
51
55
|
// Setup the listener
|
|
52
|
-
setupAuthListener(auth, store, autoAnonymousSignIn, onAuthStateChange);
|
|
56
|
+
setupAuthListener(auth, store, autoAnonymousSignIn, onAuthStateChange, getIsAnonymous);
|
|
53
57
|
completeListenerSetup();
|
|
54
58
|
|
|
55
59
|
// Return cleanup function
|
|
@@ -17,15 +17,19 @@ import type {
|
|
|
17
17
|
ProfileFormValues,
|
|
18
18
|
FormValidationError,
|
|
19
19
|
} from "./formValidation.types";
|
|
20
|
-
import {
|
|
20
|
+
import { sanitizeName } from "../../../../infrastructure/utils/validation/sanitization";
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Validate login form values.
|
|
24
|
+
* IMPORTANT: Callers must sanitize email before passing to this function.
|
|
25
|
+
*/
|
|
22
26
|
export function validateLoginForm(
|
|
23
27
|
values: LoginFormValues,
|
|
24
28
|
getErrorMessage: (key: string) => string
|
|
25
29
|
): FormValidationResult {
|
|
26
30
|
const errors: FormValidationError[] = [];
|
|
27
31
|
|
|
28
|
-
const emailResult = validateEmail(
|
|
32
|
+
const emailResult = validateEmail(values.email);
|
|
29
33
|
if (!emailResult.isValid && emailResult.error) {
|
|
30
34
|
errors.push({ field: "email", message: getErrorMessage(emailResult.error) });
|
|
31
35
|
}
|
|
@@ -38,6 +42,10 @@ export function validateLoginForm(
|
|
|
38
42
|
return { isValid: errors.length === 0, errors };
|
|
39
43
|
}
|
|
40
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Validate register form values.
|
|
47
|
+
* IMPORTANT: Callers must sanitize email before passing to this function.
|
|
48
|
+
*/
|
|
41
49
|
export function validateRegisterForm(
|
|
42
50
|
values: RegisterFormValues,
|
|
43
51
|
getErrorMessage: (key: string) => string,
|
|
@@ -45,7 +53,7 @@ export function validateRegisterForm(
|
|
|
45
53
|
): FormValidationResult {
|
|
46
54
|
const errors: FormValidationError[] = [];
|
|
47
55
|
|
|
48
|
-
const emailResult = validateEmail(
|
|
56
|
+
const emailResult = validateEmail(values.email);
|
|
49
57
|
if (!emailResult.isValid && emailResult.error) {
|
|
50
58
|
errors.push({ field: "email", message: getErrorMessage(emailResult.error) });
|
|
51
59
|
}
|
|
@@ -63,6 +71,10 @@ export function validateRegisterForm(
|
|
|
63
71
|
return { isValid: errors.length === 0, errors };
|
|
64
72
|
}
|
|
65
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Validate profile form values.
|
|
76
|
+
* Email should be pre-sanitized by caller if provided.
|
|
77
|
+
*/
|
|
66
78
|
export function validateProfileForm(
|
|
67
79
|
values: ProfileFormValues,
|
|
68
80
|
getErrorMessage: (key: string) => string
|
|
@@ -77,7 +89,7 @@ export function validateProfileForm(
|
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
if (values.email) {
|
|
80
|
-
const emailResult = validateEmail(
|
|
92
|
+
const emailResult = validateEmail(values.email);
|
|
81
93
|
if (!emailResult.isValid && emailResult.error) {
|
|
82
94
|
errors.push({ field: "email", message: getErrorMessage(emailResult.error) });
|
|
83
95
|
}
|