@umituz/react-native-auth 3.6.71 → 3.6.73
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/index.ts +1 -0
- package/src/infrastructure/adapters/StorageProviderAdapter.ts +1 -4
- package/src/infrastructure/providers/FirebaseAuthProvider.ts +5 -32
- package/src/infrastructure/repositories/AuthRepository.ts +0 -5
- package/src/infrastructure/services/AnonymousModeService.ts +6 -20
- package/src/infrastructure/services/AuthEventService.ts +2 -4
- package/src/infrastructure/services/initializeAuth.ts +2 -11
- package/src/infrastructure/utils/AuthErrorMapper.ts +0 -4
- package/src/infrastructure/utils/authStateHandler.ts +2 -4
- package/src/infrastructure/utils/listener/anonymousSignInHandler.ts +2 -22
- package/src/infrastructure/utils/listener/listenerLifecycle.util.ts +144 -0
- package/src/infrastructure/utils/listener/listenerState.util.ts +125 -0
- package/src/init/createAuthInitModule.ts +3 -22
- package/src/presentation/components/AccountActions.tsx +2 -4
- package/src/presentation/components/AuthBottomSheet.tsx +1 -17
- package/src/presentation/hooks/useAccountManagement.ts +0 -7
- package/src/presentation/hooks/useAuth.ts +2 -4
- package/src/presentation/hooks/useAuthBottomSheet.ts +23 -79
- package/src/presentation/hooks/useGoogleAuth.ts +0 -3
- package/src/presentation/hooks/useLoginForm.ts +26 -30
- package/src/presentation/hooks/useProfileEdit.ts +56 -64
- package/src/presentation/hooks/useRegisterForm.ts +41 -44
- package/src/presentation/providers/AuthProvider.tsx +2 -7
- package/src/presentation/stores/authModalStore.ts +0 -7
- package/src/presentation/stores/authStore.ts +1 -28
- package/src/presentation/stores/initializeAuthListener.ts +30 -160
- package/src/presentation/utils/accountDeleteHandler.util.ts +0 -51
- package/src/presentation/utils/authTransition.util.ts +72 -0
- package/src/presentation/utils/form/formFieldState.util.ts +82 -0
- package/src/presentation/utils/form/formValidation.util.ts +173 -0
- package/src/presentation/utils/socialAuthHandler.util.ts +106 -0
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { Platform } from "react-native";
|
|
3
2
|
import type { BottomSheetModalRef } from "@umituz/react-native-design-system";
|
|
4
3
|
import { useAuthModalStore } from "../stores/authModalStore";
|
|
5
4
|
import { useAuth } from "../hooks/useAuth";
|
|
6
5
|
import { useGoogleAuth, type GoogleAuthConfig } from "./useGoogleAuth";
|
|
7
6
|
import { useAppleAuth } from "./useAppleAuth";
|
|
8
7
|
import type { SocialAuthProvider } from "../../domain/value-objects/AuthConfig";
|
|
8
|
+
import {
|
|
9
|
+
useAuthTransitions,
|
|
10
|
+
executeAfterAuth,
|
|
11
|
+
} from "../utils/authTransition.util";
|
|
12
|
+
import { determineEnabledProviders } from "../utils/socialAuthHandler.util";
|
|
9
13
|
|
|
10
14
|
export interface SocialAuthConfiguration {
|
|
11
15
|
google?: GoogleAuthConfig;
|
|
@@ -22,7 +26,7 @@ interface UseAuthBottomSheetParams {
|
|
|
22
26
|
|
|
23
27
|
export function useAuthBottomSheet(params: UseAuthBottomSheetParams = {}) {
|
|
24
28
|
const { socialConfig, onGoogleSignIn, onAppleSignIn, onAuthSuccess } = params;
|
|
25
|
-
|
|
29
|
+
|
|
26
30
|
const modalRef = useRef<BottomSheetModalRef>(null);
|
|
27
31
|
const [googleLoading, setGoogleLoading] = useState(false);
|
|
28
32
|
const [appleLoading, setAppleLoading] = useState(false);
|
|
@@ -37,37 +41,14 @@ export function useAuthBottomSheet(params: UseAuthBottomSheetParams = {}) {
|
|
|
37
41
|
|
|
38
42
|
// Determine enabled providers
|
|
39
43
|
const providers = useMemo<SocialAuthProvider[]>(() => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (Platform.OS === "ios" && socialConfig?.apple?.enabled && appleAvailable) {
|
|
43
|
-
result.push("apple");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (googleConfigured) {
|
|
47
|
-
result.push("google");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return result;
|
|
51
|
-
}, [socialConfig?.apple?.enabled, appleAvailable, googleConfigured]);
|
|
44
|
+
return determineEnabledProviders(socialConfig, appleAvailable, googleConfigured);
|
|
45
|
+
}, [socialConfig, appleAvailable, googleConfigured]);
|
|
52
46
|
|
|
53
47
|
// Handle visibility sync with modalRef
|
|
54
48
|
useEffect(() => {
|
|
55
|
-
if (__DEV__) {
|
|
56
|
-
console.log('[useAuthBottomSheet] Visibility changed:', {
|
|
57
|
-
isVisible,
|
|
58
|
-
hasModalRef: !!modalRef.current,
|
|
59
|
-
modalRefMethods: modalRef.current ? Object.keys(modalRef.current) : [],
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
49
|
if (isVisible) {
|
|
63
|
-
if (__DEV__) {
|
|
64
|
-
console.log('[useAuthBottomSheet] Calling present()');
|
|
65
|
-
}
|
|
66
50
|
modalRef.current?.present();
|
|
67
51
|
} else {
|
|
68
|
-
if (__DEV__) {
|
|
69
|
-
console.log('[useAuthBottomSheet] Calling dismiss()');
|
|
70
|
-
}
|
|
71
52
|
modalRef.current?.dismiss();
|
|
72
53
|
}
|
|
73
54
|
}, [isVisible]);
|
|
@@ -82,61 +63,24 @@ export function useAuthBottomSheet(params: UseAuthBottomSheetParams = {}) {
|
|
|
82
63
|
handleDismiss();
|
|
83
64
|
}, [handleDismiss]);
|
|
84
65
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
isAnonymous,
|
|
97
|
-
isVisible,
|
|
98
|
-
prevIsAuthenticated: prevIsAuthenticatedRef.current,
|
|
99
|
-
prevIsAnonymous: prevIsAnonymousRef.current,
|
|
100
|
-
justAuthenticated,
|
|
101
|
-
justConvertedFromAnonymous,
|
|
102
|
-
willClose: (justAuthenticated || justConvertedFromAnonymous) && isVisible && !isAnonymous,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
let timeoutId: NodeJS.Timeout | undefined;
|
|
107
|
-
|
|
108
|
-
if ((justAuthenticated || justConvertedFromAnonymous) && isVisible && !isAnonymous) {
|
|
109
|
-
if (__DEV__) {
|
|
110
|
-
console.log("[useAuthBottomSheet] Auto-closing due to successful authentication transition", {
|
|
111
|
-
justAuthenticated,
|
|
112
|
-
justConvertedFromAnonymous,
|
|
66
|
+
// Handle auth transitions
|
|
67
|
+
useAuthTransitions(
|
|
68
|
+
{ isAuthenticated, isAnonymous, isVisible },
|
|
69
|
+
(result) => {
|
|
70
|
+
if (result.shouldClose) {
|
|
71
|
+
modalRef.current?.dismiss();
|
|
72
|
+
hideAuthModal();
|
|
73
|
+
onAuthSuccess?.();
|
|
74
|
+
|
|
75
|
+
const timeoutId = executeAfterAuth(() => {
|
|
76
|
+
executePendingCallback();
|
|
113
77
|
});
|
|
114
|
-
}
|
|
115
|
-
// Close modal and hide first
|
|
116
|
-
modalRef.current?.dismiss();
|
|
117
|
-
hideAuthModal();
|
|
118
|
-
// Notify auth success
|
|
119
|
-
onAuthSuccess?.();
|
|
120
|
-
// Execute callback with delay to ensure auth state has propagated
|
|
121
|
-
timeoutId = setTimeout(() => {
|
|
122
|
-
if (__DEV__) {
|
|
123
|
-
console.log("[useAuthBottomSheet] Executing pending callback after auth");
|
|
124
|
-
}
|
|
125
|
-
executePendingCallback();
|
|
126
|
-
}, 100);
|
|
127
|
-
}
|
|
128
78
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
prevIsAnonymousRef.current = isAnonymous;
|
|
132
|
-
|
|
133
|
-
// Cleanup timeout on unmount or dependency change
|
|
134
|
-
return () => {
|
|
135
|
-
if (timeoutId) {
|
|
136
|
-
clearTimeout(timeoutId);
|
|
79
|
+
// Cleanup timeout on unmount
|
|
80
|
+
return () => clearTimeout(timeoutId);
|
|
137
81
|
}
|
|
138
|
-
}
|
|
139
|
-
|
|
82
|
+
}
|
|
83
|
+
);
|
|
140
84
|
|
|
141
85
|
const handleNavigateToRegister = useCallback(() => {
|
|
142
86
|
setMode("register");
|
|
@@ -87,9 +87,6 @@ export function useGoogleAuth(config?: GoogleAuthConfig): UseGoogleAuthResult {
|
|
|
87
87
|
.catch((error) => {
|
|
88
88
|
const errorMessage = error instanceof Error ? error.message : "Firebase sign-in failed";
|
|
89
89
|
setGoogleError(errorMessage);
|
|
90
|
-
if (__DEV__) {
|
|
91
|
-
console.error("[useGoogleAuth] Firebase sign-in failed:", error);
|
|
92
|
-
}
|
|
93
90
|
})
|
|
94
91
|
.finally(() => {
|
|
95
92
|
setIsLoading(false);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useState, useCallback } from "react";
|
|
2
2
|
import { useAuth } from "./useAuth";
|
|
3
3
|
import { getAuthErrorLocalizationKey, resolveErrorMessage } from "../utils/getAuthErrorMessage";
|
|
4
|
-
import {
|
|
4
|
+
import { validateLoginForm } from "../utils/form/formValidation.util";
|
|
5
5
|
import { alertService } from "@umituz/react-native-design-system";
|
|
6
6
|
|
|
7
7
|
export interface LoginFormTranslations {
|
|
@@ -42,45 +42,44 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
|
|
|
42
42
|
return resolveErrorMessage(key, translations?.errors);
|
|
43
43
|
}, [translations]);
|
|
44
44
|
|
|
45
|
+
const clearErrors = useCallback(() => {
|
|
46
|
+
setEmailError(null);
|
|
47
|
+
setPasswordError(null);
|
|
48
|
+
setLocalError(null);
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
45
51
|
const handleEmailChange = useCallback(
|
|
46
52
|
(text: string) => {
|
|
47
53
|
setEmail(text);
|
|
48
|
-
if (emailError)
|
|
49
|
-
if (localError) setLocalError(null);
|
|
54
|
+
if (emailError || localError) clearErrors();
|
|
50
55
|
},
|
|
51
|
-
[emailError, localError],
|
|
56
|
+
[emailError, localError, clearErrors],
|
|
52
57
|
);
|
|
53
58
|
|
|
54
59
|
const handlePasswordChange = useCallback(
|
|
55
60
|
(text: string) => {
|
|
56
61
|
setPassword(text);
|
|
57
|
-
if (passwordError)
|
|
58
|
-
if (localError) setLocalError(null);
|
|
62
|
+
if (passwordError || localError) clearErrors();
|
|
59
63
|
},
|
|
60
|
-
[passwordError, localError],
|
|
64
|
+
[passwordError, localError, clearErrors],
|
|
61
65
|
);
|
|
62
66
|
|
|
63
67
|
const handleSignIn = useCallback(async () => {
|
|
64
|
-
|
|
65
|
-
setPasswordError(null);
|
|
66
|
-
setLocalError(null);
|
|
67
|
-
|
|
68
|
-
let hasError = false;
|
|
68
|
+
clearErrors();
|
|
69
69
|
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
70
|
+
const validation = validateLoginForm(
|
|
71
|
+
{ email: email.trim(), password },
|
|
72
|
+
getErrorMessage
|
|
73
|
+
);
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
75
|
+
if (!validation.isValid) {
|
|
76
|
+
for (const error of validation.errors) {
|
|
77
|
+
if (error.field === "email") setEmailError(error.message);
|
|
78
|
+
if (error.field === "password") setPasswordError(error.message);
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
if (hasError) return;
|
|
83
|
-
|
|
84
83
|
try {
|
|
85
84
|
await signIn(email.trim(), password);
|
|
86
85
|
|
|
@@ -92,18 +91,15 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
|
|
|
92
91
|
}
|
|
93
92
|
} catch (err: unknown) {
|
|
94
93
|
const localizationKey = getAuthErrorLocalizationKey(err);
|
|
95
|
-
|
|
96
|
-
setLocalError(errorMessage);
|
|
94
|
+
setLocalError(getErrorMessage(localizationKey));
|
|
97
95
|
}
|
|
98
|
-
}, [email, password, signIn, translations, getErrorMessage]);
|
|
96
|
+
}, [email, password, signIn, translations, getErrorMessage, clearErrors]);
|
|
99
97
|
|
|
100
98
|
const handleContinueAnonymously = useCallback(async () => {
|
|
101
99
|
try {
|
|
102
100
|
await continueAnonymously();
|
|
103
|
-
} catch
|
|
104
|
-
|
|
105
|
-
console.warn("[useLoginForm] Continue anonymously failed:", error);
|
|
106
|
-
}
|
|
101
|
+
} catch {
|
|
102
|
+
// Silently fail - anonymous mode is optional
|
|
107
103
|
}
|
|
108
104
|
}, [continueAnonymously]);
|
|
109
105
|
|
|
@@ -1,88 +1,80 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Profile Edit Hook
|
|
3
3
|
* Simple profile editing with form state management
|
|
4
4
|
* Apps provide image picker and backend update logic
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { useState, useCallback } from "react";
|
|
8
|
-
import {
|
|
8
|
+
import { validateProfileForm } from "../utils/form/formValidation.util";
|
|
9
9
|
|
|
10
10
|
export interface ProfileEditFormState {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
displayName: string;
|
|
12
|
+
email: string;
|
|
13
|
+
photoURL: string | null;
|
|
14
|
+
isModified: boolean;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export interface UseProfileEditReturn {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
formState: ProfileEditFormState;
|
|
19
|
+
setDisplayName: (value: string) => void;
|
|
20
|
+
setEmail: (value: string) => void;
|
|
21
|
+
setPhotoURL: (value: string | null) => void;
|
|
22
|
+
resetForm: (initial: Partial<ProfileEditFormState>) => void;
|
|
23
|
+
validateForm: () => { isValid: boolean; errors: string[] };
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export const useProfileEdit = (
|
|
27
|
-
|
|
27
|
+
initialState: Partial<ProfileEditFormState> = {},
|
|
28
28
|
): UseProfileEditReturn => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const setDisplayName = useCallback((value: string) => {
|
|
37
|
-
setFormState((prev) => ({ ...prev, displayName: value, isModified: true }));
|
|
38
|
-
}, []);
|
|
39
|
-
|
|
40
|
-
const setEmail = useCallback((value: string) => {
|
|
41
|
-
setFormState((prev) => ({ ...prev, email: value, isModified: true }));
|
|
42
|
-
}, []);
|
|
43
|
-
|
|
44
|
-
const setPhotoURL = useCallback((value: string | null) => {
|
|
45
|
-
setFormState((prev) => ({ ...prev, photoURL: value, isModified: true }));
|
|
46
|
-
}, []);
|
|
29
|
+
const [formState, setFormState] = useState<ProfileEditFormState>({
|
|
30
|
+
displayName: initialState.displayName || "",
|
|
31
|
+
email: initialState.email || "",
|
|
32
|
+
photoURL: initialState.photoURL || null,
|
|
33
|
+
isModified: false,
|
|
34
|
+
});
|
|
47
35
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
email: initial.email || "",
|
|
52
|
-
photoURL: initial.photoURL || null,
|
|
53
|
-
isModified: false,
|
|
54
|
-
});
|
|
55
|
-
}, []);
|
|
36
|
+
const setDisplayName = useCallback((value: string) => {
|
|
37
|
+
setFormState((prev) => ({ ...prev, displayName: value, isModified: true }));
|
|
38
|
+
}, []);
|
|
56
39
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
} => {
|
|
61
|
-
const errors: string[] = [];
|
|
40
|
+
const setEmail = useCallback((value: string) => {
|
|
41
|
+
setFormState((prev) => ({ ...prev, email: value, isModified: true }));
|
|
42
|
+
}, []);
|
|
62
43
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
44
|
+
const setPhotoURL = useCallback((value: string | null) => {
|
|
45
|
+
setFormState((prev) => ({ ...prev, photoURL: value, isModified: true }));
|
|
46
|
+
}, []);
|
|
66
47
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
48
|
+
const resetForm = useCallback((initial: Partial<ProfileEditFormState>) => {
|
|
49
|
+
setFormState({
|
|
50
|
+
displayName: initial.displayName || "",
|
|
51
|
+
email: initial.email || "",
|
|
52
|
+
photoURL: initial.photoURL || null,
|
|
53
|
+
isModified: false,
|
|
54
|
+
});
|
|
55
|
+
}, []);
|
|
73
56
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
57
|
+
const validateForm = useCallback((): {
|
|
58
|
+
isValid: boolean;
|
|
59
|
+
errors: string[];
|
|
60
|
+
} => {
|
|
61
|
+
const result = validateProfileForm({
|
|
62
|
+
displayName: formState.displayName,
|
|
63
|
+
email: formState.email,
|
|
64
|
+
});
|
|
79
65
|
|
|
80
66
|
return {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
setEmail,
|
|
84
|
-
setPhotoURL,
|
|
85
|
-
resetForm,
|
|
86
|
-
validateForm,
|
|
67
|
+
isValid: result.isValid,
|
|
68
|
+
errors: result.errors.map((e) => e.message),
|
|
87
69
|
};
|
|
70
|
+
}, [formState]);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
formState,
|
|
74
|
+
setDisplayName,
|
|
75
|
+
setEmail,
|
|
76
|
+
setPhotoURL,
|
|
77
|
+
resetForm,
|
|
78
|
+
validateForm,
|
|
79
|
+
};
|
|
88
80
|
};
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { useState, useCallback, useMemo } from "react";
|
|
2
2
|
import {
|
|
3
|
-
validateEmail,
|
|
4
3
|
validatePasswordForRegister,
|
|
5
|
-
|
|
4
|
+
type PasswordRequirements,
|
|
6
5
|
} from "../../infrastructure/utils/AuthValidation";
|
|
7
6
|
import { DEFAULT_PASSWORD_CONFIG } from "../../domain/value-objects/AuthConfig";
|
|
8
7
|
import { useAuth } from "./useAuth";
|
|
9
8
|
import { getAuthErrorLocalizationKey, resolveErrorMessage } from "../utils/getAuthErrorMessage";
|
|
10
|
-
import
|
|
9
|
+
import { validateRegisterForm, errorsToFieldErrors } from "../utils/form/formValidation.util";
|
|
11
10
|
import { alertService } from "@umituz/react-native-design-system";
|
|
12
11
|
import { clearFieldErrors, clearFieldError } from "../utils/form/formErrorUtils";
|
|
13
12
|
|
|
13
|
+
export type FieldErrors = {
|
|
14
|
+
displayName?: string;
|
|
15
|
+
email?: string;
|
|
16
|
+
password?: string;
|
|
17
|
+
confirmPassword?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
14
20
|
export interface RegisterFormTranslations {
|
|
15
21
|
successTitle: string;
|
|
16
22
|
signUpSuccess: string;
|
|
@@ -26,12 +32,7 @@ export interface UseRegisterFormResult {
|
|
|
26
32
|
email: string;
|
|
27
33
|
password: string;
|
|
28
34
|
confirmPassword: string;
|
|
29
|
-
fieldErrors:
|
|
30
|
-
displayName?: string;
|
|
31
|
-
email?: string;
|
|
32
|
-
password?: string;
|
|
33
|
-
confirmPassword?: string;
|
|
34
|
-
};
|
|
35
|
+
fieldErrors: FieldErrors;
|
|
35
36
|
localError: string | null;
|
|
36
37
|
loading: boolean;
|
|
37
38
|
passwordRequirements: PasswordRequirements;
|
|
@@ -53,12 +54,7 @@ export function useRegisterForm(config?: UseRegisterFormConfig): UseRegisterForm
|
|
|
53
54
|
const [password, setPassword] = useState("");
|
|
54
55
|
const [confirmPassword, setConfirmPassword] = useState("");
|
|
55
56
|
const [localError, setLocalError] = useState<string | null>(null);
|
|
56
|
-
const [fieldErrors, setFieldErrors] = useState<{
|
|
57
|
-
displayName?: string;
|
|
58
|
-
email?: string;
|
|
59
|
-
password?: string;
|
|
60
|
-
confirmPassword?: string;
|
|
61
|
-
}>({});
|
|
57
|
+
const [fieldErrors, setFieldErrors] = useState<FieldErrors>({});
|
|
62
58
|
|
|
63
59
|
const getErrorMessage = useCallback((key: string) => {
|
|
64
60
|
return resolveErrorMessage(key, translations?.errors);
|
|
@@ -76,49 +72,51 @@ export function useRegisterForm(config?: UseRegisterFormConfig): UseRegisterForm
|
|
|
76
72
|
return password.length > 0 && password === confirmPassword;
|
|
77
73
|
}, [password, confirmPassword]);
|
|
78
74
|
|
|
75
|
+
const clearFormErrors = useCallback(() => {
|
|
76
|
+
setLocalError(null);
|
|
77
|
+
setFieldErrors({});
|
|
78
|
+
}, []);
|
|
79
|
+
|
|
79
80
|
const handleDisplayNameChange = useCallback((text: string) => {
|
|
80
81
|
setDisplayName(text);
|
|
81
82
|
clearFieldError(setFieldErrors, "displayName");
|
|
82
|
-
setLocalError(null);
|
|
83
|
-
}, []);
|
|
83
|
+
if (localError) setLocalError(null);
|
|
84
|
+
}, [localError]);
|
|
84
85
|
|
|
85
86
|
const handleEmailChange = useCallback((text: string) => {
|
|
86
87
|
setEmail(text);
|
|
87
88
|
clearFieldError(setFieldErrors, "email");
|
|
88
|
-
setLocalError(null);
|
|
89
|
-
}, []);
|
|
89
|
+
if (localError) setLocalError(null);
|
|
90
|
+
}, [localError]);
|
|
90
91
|
|
|
91
92
|
const handlePasswordChange = useCallback((text: string) => {
|
|
92
93
|
setPassword(text);
|
|
93
94
|
clearFieldErrors(setFieldErrors, ["password", "confirmPassword"]);
|
|
94
|
-
setLocalError(null);
|
|
95
|
-
}, []);
|
|
95
|
+
if (localError) setLocalError(null);
|
|
96
|
+
}, [localError]);
|
|
96
97
|
|
|
97
98
|
const handleConfirmPasswordChange = useCallback((text: string) => {
|
|
98
99
|
setConfirmPassword(text);
|
|
99
100
|
clearFieldError(setFieldErrors, "confirmPassword");
|
|
100
|
-
setLocalError(null);
|
|
101
|
-
}, []);
|
|
101
|
+
if (localError) setLocalError(null);
|
|
102
|
+
}, [localError]);
|
|
102
103
|
|
|
103
104
|
const handleSignUp = useCallback(async () => {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const confirmResult = validatePasswordConfirmation(password, confirmPassword);
|
|
120
|
-
if (!confirmResult.isValid && confirmResult.error) {
|
|
121
|
-
setFieldErrors((prev) => ({ ...prev, confirmPassword: getErrorMessage(confirmResult.error!) }));
|
|
105
|
+
clearFormErrors();
|
|
106
|
+
|
|
107
|
+
const validation = validateRegisterForm(
|
|
108
|
+
{
|
|
109
|
+
displayName: displayName.trim() || undefined,
|
|
110
|
+
email: email.trim(),
|
|
111
|
+
password,
|
|
112
|
+
confirmPassword,
|
|
113
|
+
},
|
|
114
|
+
getErrorMessage,
|
|
115
|
+
DEFAULT_PASSWORD_CONFIG
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (!validation.isValid) {
|
|
119
|
+
setFieldErrors(errorsToFieldErrors(validation.errors));
|
|
122
120
|
return;
|
|
123
121
|
}
|
|
124
122
|
|
|
@@ -130,10 +128,9 @@ export function useRegisterForm(config?: UseRegisterFormConfig): UseRegisterForm
|
|
|
130
128
|
}
|
|
131
129
|
} catch (err: unknown) {
|
|
132
130
|
const localizationKey = getAuthErrorLocalizationKey(err);
|
|
133
|
-
|
|
134
|
-
setLocalError(errorMessage);
|
|
131
|
+
setLocalError(getErrorMessage(localizationKey));
|
|
135
132
|
}
|
|
136
|
-
}, [displayName, email, password, confirmPassword, signUp, translations, getErrorMessage]);
|
|
133
|
+
}, [displayName, email, password, confirmPassword, signUp, translations, getErrorMessage, clearFormErrors]);
|
|
137
134
|
|
|
138
135
|
const displayError = localError || error;
|
|
139
136
|
|
|
@@ -66,9 +66,6 @@ export function AuthProvider({ children, ErrorFallback = DefaultErrorFallback }:
|
|
|
66
66
|
unsubscribe = initializeAuthListener();
|
|
67
67
|
} catch (err) {
|
|
68
68
|
const errorObj = err instanceof Error ? err : new Error("Unknown initialization error");
|
|
69
|
-
if (__DEV__) {
|
|
70
|
-
console.error("[AuthProvider] Initialization failed:", errorObj);
|
|
71
|
-
}
|
|
72
69
|
setError(errorObj);
|
|
73
70
|
}
|
|
74
71
|
|
|
@@ -76,10 +73,8 @@ export function AuthProvider({ children, ErrorFallback = DefaultErrorFallback }:
|
|
|
76
73
|
if (unsubscribe) {
|
|
77
74
|
try {
|
|
78
75
|
unsubscribe();
|
|
79
|
-
} catch
|
|
80
|
-
|
|
81
|
-
console.warn("[AuthProvider] Cleanup failed:", cleanupError);
|
|
82
|
-
}
|
|
76
|
+
} catch {
|
|
77
|
+
// Silently fail - cleanup errors are handled elsewhere
|
|
83
78
|
}
|
|
84
79
|
}
|
|
85
80
|
};
|
|
@@ -53,13 +53,6 @@ export const useAuthModalStore = createStore<AuthModalState, AuthModalActions>({
|
|
|
53
53
|
callback?: () => void | Promise<void>,
|
|
54
54
|
mode: AuthModalMode = "login",
|
|
55
55
|
) => {
|
|
56
|
-
if (__DEV__) {
|
|
57
|
-
console.log("[authModalStore] showAuthModal called:", {
|
|
58
|
-
mode,
|
|
59
|
-
hasCallback: !!callback,
|
|
60
|
-
currentVisible: get().isVisible,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
56
|
set({
|
|
64
57
|
isVisible: true,
|
|
65
58
|
mode,
|
|
@@ -45,36 +45,18 @@ export const useAuthStore = createStore<AuthState, AuthActions>({
|
|
|
45
45
|
}
|
|
46
46
|
return { ...initialAuthState, ...state };
|
|
47
47
|
},
|
|
48
|
-
actions: (set,
|
|
48
|
+
actions: (set, _get) => ({
|
|
49
49
|
setFirebaseUser: (firebaseUser) => {
|
|
50
|
-
const prevState = get();
|
|
51
50
|
const user = firebaseUser ? mapToAuthUser(firebaseUser) : null;
|
|
52
51
|
const isAnonymous = firebaseUser?.isAnonymous ?? false;
|
|
53
|
-
|
|
54
|
-
if (__DEV__) {
|
|
55
|
-
console.log("[AuthStore] setFirebaseUser:", {
|
|
56
|
-
uid: firebaseUser?.uid ?? null,
|
|
57
|
-
isAnonymous,
|
|
58
|
-
prevIsAnonymous: prevState.isAnonymous,
|
|
59
|
-
hasUser: !!user,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
52
|
set({ firebaseUser, user, loading: false, isAnonymous });
|
|
64
53
|
},
|
|
65
54
|
|
|
66
55
|
setLoading: (loading) => {
|
|
67
|
-
if (__DEV__) {
|
|
68
|
-
console.log("[AuthStore] setLoading:", loading);
|
|
69
|
-
}
|
|
70
56
|
set({ loading });
|
|
71
57
|
},
|
|
72
58
|
|
|
73
59
|
setIsAnonymous: (isAnonymous) => {
|
|
74
|
-
const { firebaseUser } = get();
|
|
75
|
-
if (__DEV__) {
|
|
76
|
-
console.log("[AuthStore] setIsAnonymous:", { isAnonymous, hasFirebaseUser: !!firebaseUser });
|
|
77
|
-
}
|
|
78
60
|
// Only update the isAnonymous flag
|
|
79
61
|
// The user object will be updated by setFirebaseUser when needed
|
|
80
62
|
// This prevents inconsistencies between firebaseUser and user
|
|
@@ -82,23 +64,14 @@ export const useAuthStore = createStore<AuthState, AuthActions>({
|
|
|
82
64
|
},
|
|
83
65
|
|
|
84
66
|
setError: (error) => {
|
|
85
|
-
if (__DEV__ && error) {
|
|
86
|
-
console.log("[AuthStore] setError:", error);
|
|
87
|
-
}
|
|
88
67
|
set({ error });
|
|
89
68
|
},
|
|
90
69
|
|
|
91
70
|
setInitialized: (initialized) => {
|
|
92
|
-
if (__DEV__) {
|
|
93
|
-
console.log("[AuthStore] setInitialized:", initialized);
|
|
94
|
-
}
|
|
95
71
|
set({ initialized });
|
|
96
72
|
},
|
|
97
73
|
|
|
98
74
|
reset: () => {
|
|
99
|
-
if (__DEV__) {
|
|
100
|
-
console.log("[AuthStore] reset");
|
|
101
|
-
}
|
|
102
75
|
set(initialAuthState);
|
|
103
76
|
},
|
|
104
77
|
}),
|