@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.
Files changed (32) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +1 -0
  3. package/src/infrastructure/adapters/StorageProviderAdapter.ts +1 -4
  4. package/src/infrastructure/providers/FirebaseAuthProvider.ts +5 -32
  5. package/src/infrastructure/repositories/AuthRepository.ts +0 -5
  6. package/src/infrastructure/services/AnonymousModeService.ts +6 -20
  7. package/src/infrastructure/services/AuthEventService.ts +2 -4
  8. package/src/infrastructure/services/initializeAuth.ts +2 -11
  9. package/src/infrastructure/utils/AuthErrorMapper.ts +0 -4
  10. package/src/infrastructure/utils/authStateHandler.ts +2 -4
  11. package/src/infrastructure/utils/listener/anonymousSignInHandler.ts +2 -22
  12. package/src/infrastructure/utils/listener/listenerLifecycle.util.ts +144 -0
  13. package/src/infrastructure/utils/listener/listenerState.util.ts +125 -0
  14. package/src/init/createAuthInitModule.ts +3 -22
  15. package/src/presentation/components/AccountActions.tsx +2 -4
  16. package/src/presentation/components/AuthBottomSheet.tsx +1 -17
  17. package/src/presentation/hooks/useAccountManagement.ts +0 -7
  18. package/src/presentation/hooks/useAuth.ts +2 -4
  19. package/src/presentation/hooks/useAuthBottomSheet.ts +23 -79
  20. package/src/presentation/hooks/useGoogleAuth.ts +0 -3
  21. package/src/presentation/hooks/useLoginForm.ts +26 -30
  22. package/src/presentation/hooks/useProfileEdit.ts +56 -64
  23. package/src/presentation/hooks/useRegisterForm.ts +41 -44
  24. package/src/presentation/providers/AuthProvider.tsx +2 -7
  25. package/src/presentation/stores/authModalStore.ts +0 -7
  26. package/src/presentation/stores/authStore.ts +1 -28
  27. package/src/presentation/stores/initializeAuthListener.ts +30 -160
  28. package/src/presentation/utils/accountDeleteHandler.util.ts +0 -51
  29. package/src/presentation/utils/authTransition.util.ts +72 -0
  30. package/src/presentation/utils/form/formFieldState.util.ts +82 -0
  31. package/src/presentation/utils/form/formValidation.util.ts +173 -0
  32. package/src/presentation/utils/socialAuthHandler.util.ts +106 -0
@@ -4,18 +4,22 @@
4
4
  * Uses onIdTokenChanged for profile updates (displayName, email)
5
5
  */
6
6
 
7
- import { onIdTokenChanged } from "firebase/auth";
8
7
  import { getFirebaseAuth } from "@umituz/react-native-firebase";
9
8
  import { useAuthStore } from "./authStore";
10
- import { getAuthService } from "../../infrastructure/services/AuthService";
11
9
  import type { AuthListenerOptions } from "../../types/auth-store.types";
12
- import { createAnonymousSignInHandler } from "../../infrastructure/utils/listener/anonymousSignInHandler";
13
-
14
- let listenerInitialized = false;
15
- let listenerRefCount = 0;
16
- let firebaseUnsubscribe: (() => void) | null = null;
17
- let anonymousSignInInProgress = false; // Prevent race conditions
18
- let initializationInProgress = false; // Prevent duplicate initialization
10
+ import {
11
+ handleExistingInitialization,
12
+ handleInitializationInProgress,
13
+ handleNoFirebaseAuth,
14
+ setupAuthListener,
15
+ completeListenerSetup,
16
+ } from "../../infrastructure/utils/listener/listenerLifecycle.util";
17
+ import {
18
+ isInitializationInProgress,
19
+ isListenerInitialized,
20
+ resetListenerState,
21
+ decrementRefCount,
22
+ } from "../../infrastructure/utils/listener/listenerState.util";
19
23
 
20
24
  /**
21
25
  * Initialize Firebase auth listener
@@ -26,160 +30,33 @@ export function initializeAuthListener(
26
30
  ): () => void {
27
31
  const { autoAnonymousSignIn = true, onAuthStateChange } = options;
28
32
 
29
- if (__DEV__) {
30
- console.log("[AuthListener] initializeAuthListener called:", {
31
- autoAnonymousSignIn,
32
- alreadyInitialized: listenerInitialized,
33
- initializationInProgress,
34
- });
33
+ // Prevent duplicate initialization
34
+ if (isInitializationInProgress()) {
35
+ return handleInitializationInProgress();
35
36
  }
36
37
 
37
- // Prevent duplicate initialization - return existing unsubscribe if already initializing
38
- if (initializationInProgress) {
39
- if (__DEV__) {
40
- console.warn("[AuthListener] Initialization already in progress, returning existing unsubscribe");
41
- }
42
- return () => {
43
- // No-op - will be handled by the initial initialization
44
- };
38
+ // If already initialized, increment ref count and return unsubscribe
39
+ if (isListenerInitialized()) {
40
+ return handleExistingInitialization()!;
45
41
  }
46
42
 
47
- // If already initialized, increment ref count and return unsubscribe that decrements
48
- if (listenerInitialized) {
49
- listenerRefCount++;
50
- if (__DEV__) {
51
- console.log("[AuthListener] Already initialized, incrementing ref count:", listenerRefCount);
52
- }
53
- // Return function that decrements ref count
54
- return () => {
55
- listenerRefCount--;
56
- if (__DEV__) {
57
- console.log("[AuthListener] Ref count decremented:", listenerRefCount);
58
- }
59
- // Only cleanup when all subscribers unsubscribe
60
- if (listenerRefCount <= 0 && firebaseUnsubscribe) {
61
- if (__DEV__) {
62
- console.log("[AuthListener] Last subscriber, cleaning up");
63
- }
64
- firebaseUnsubscribe();
65
- firebaseUnsubscribe = null;
66
- listenerInitialized = false;
67
- listenerRefCount = 0;
68
- anonymousSignInInProgress = false;
69
- }
70
- };
71
- }
72
-
73
- // Mark initialization as in progress
74
- initializationInProgress = true;
75
-
76
43
  const auth = getFirebaseAuth();
77
44
  const store = useAuthStore.getState();
78
45
 
79
46
  if (!auth) {
80
- if (__DEV__) {
81
- console.log("[AuthListener] No Firebase auth, marking initialized");
82
- }
83
- initializationInProgress = false;
84
- store.setLoading(false);
85
- store.setInitialized(true);
86
- return () => {};
47
+ return handleNoFirebaseAuth(store);
87
48
  }
88
49
 
89
- listenerInitialized = true;
90
- listenerRefCount = 1;
91
-
92
- // Initialize listener first, then check anonymous mode
93
- // This prevents race conditions where the listener fires before we set up state
94
- const service = getAuthService();
95
- if (service) {
96
- const isAnonymous = service.getIsAnonymousMode();
97
- if (__DEV__) {
98
- console.log("[AuthListener] Service isAnonymousMode:", isAnonymous);
99
- }
100
- // Set anonymous mode flag before setting up listener
101
- // This ensures consistent state when listener first fires
102
- if (isAnonymous) {
103
- store.setIsAnonymous(true);
104
- }
105
- }
106
-
107
- firebaseUnsubscribe = onIdTokenChanged(auth, (user) => {
108
- if (__DEV__) {
109
- console.log("[AuthListener] onIdTokenChanged:", {
110
- uid: user?.uid ?? null,
111
- isAnonymous: user?.isAnonymous ?? null,
112
- email: user?.email ?? null,
113
- displayName: user?.displayName ?? null,
114
- });
115
- }
116
-
117
- if (!user && autoAnonymousSignIn) {
118
- // Prevent race condition: only one anonymous sign-in at a time
119
- if (anonymousSignInInProgress) {
120
- if (__DEV__) {
121
- console.log("[AuthListener] Anonymous sign-in already in progress, skipping");
122
- }
123
- store.setFirebaseUser(null);
124
- return;
125
- }
126
-
127
- if (__DEV__) {
128
- console.log("[AuthListener] No user, auto signing in anonymously...");
129
- }
130
-
131
- anonymousSignInInProgress = true;
132
-
133
- // Create and execute anonymous sign-in handler
134
- const handleAnonymousSignIn = createAnonymousSignInHandler(auth, store);
135
-
136
- // Start sign-in without blocking the listener
137
- void (async () => {
138
- try {
139
- await handleAnonymousSignIn();
140
- } finally {
141
- anonymousSignInInProgress = false;
142
- }
143
- })();
144
-
145
- // Continue execution - don't return early
146
- // The listener will be triggered again when sign-in succeeds
147
- store.setFirebaseUser(null);
148
- initializationInProgress = false;
149
- return;
150
- }
151
-
152
- store.setFirebaseUser(user);
153
- store.setInitialized(true);
154
-
155
- if (user && !user.isAnonymous && store.isAnonymous) {
156
- if (__DEV__) {
157
- console.log("[AuthListener] User converted from anonymous, updating");
158
- }
159
- store.setIsAnonymous(false);
160
- }
161
-
162
- onAuthStateChange?.(user);
163
- });
164
-
165
- initializationInProgress = false;
50
+ // Setup the listener
51
+ setupAuthListener(auth, store, autoAnonymousSignIn, onAuthStateChange);
52
+ completeListenerSetup();
166
53
 
54
+ // Return cleanup function
167
55
  return () => {
168
- listenerRefCount--;
169
- if (__DEV__) {
170
- console.log("[AuthListener] Unsubscribing, ref count:", listenerRefCount);
171
- }
172
- // Only cleanup when all subscribers unsubscribe
173
- if (listenerRefCount <= 0 && firebaseUnsubscribe) {
174
- if (__DEV__) {
175
- console.log("[AuthListener] Last subscriber, cleaning up listener");
176
- }
177
- firebaseUnsubscribe();
178
- firebaseUnsubscribe = null;
179
- listenerInitialized = false;
180
- listenerRefCount = 0;
181
- anonymousSignInInProgress = false;
182
- initializationInProgress = false;
56
+ const { shouldCleanup } = decrementRefCount();
57
+
58
+ if (shouldCleanup) {
59
+ resetListenerState();
183
60
  }
184
61
  };
185
62
  }
@@ -188,19 +65,12 @@ export function initializeAuthListener(
188
65
  * Reset listener state (for testing)
189
66
  */
190
67
  export function resetAuthListener(): void {
191
- if (firebaseUnsubscribe) {
192
- firebaseUnsubscribe();
193
- firebaseUnsubscribe = null;
194
- }
195
- listenerInitialized = false;
196
- listenerRefCount = 0;
197
- anonymousSignInInProgress = false;
198
- initializationInProgress = false;
68
+ resetListenerState();
199
69
  }
200
70
 
201
71
  /**
202
72
  * Check if listener is initialized
203
73
  */
204
74
  export function isAuthListenerInitialized(): boolean {
205
- return listenerInitialized;
75
+ return isListenerInitialized();
206
76
  }
@@ -21,20 +21,9 @@ export interface DeleteAccountOptions {
21
21
  export async function handleAccountDeletion(
22
22
  callbacks: DeleteAccountCallbacks
23
23
  ): Promise<void> {
24
- if (__DEV__) {
25
- console.log("[AccountDeleteHandler] Starting deletion with auto-reauthenticate");
26
- }
27
-
28
24
  const result = await deleteCurrentUser({ autoReauthenticate: true });
29
25
 
30
- if (__DEV__) {
31
- console.log("[AccountDeleteHandler] First attempt result:", result);
32
- }
33
-
34
26
  if (result.success) {
35
- if (__DEV__) {
36
- console.log("[AccountDeleteHandler] Delete successful");
37
- }
38
27
  return;
39
28
  }
40
29
 
@@ -44,9 +33,6 @@ export async function handleAccountDeletion(
44
33
  }
45
34
 
46
35
  if (result.error) {
47
- if (__DEV__) {
48
- console.log("[AccountDeleteHandler] Delete failed:", result.error);
49
- }
50
36
  throw new Error(result.error.message);
51
37
  }
52
38
  }
@@ -71,36 +57,18 @@ async function handleReauthentication(
71
57
  }
72
58
 
73
59
  async function retryWithPassword(onPasswordRequired: () => Promise<string | null>): Promise<void> {
74
- if (__DEV__) {
75
- console.log("[AccountDeleteHandler] Prompting for password");
76
- }
77
-
78
60
  const password = await onPasswordRequired();
79
61
 
80
62
  if (!password) {
81
- if (__DEV__) {
82
- console.log("[AccountDeleteHandler] Password prompt cancelled");
83
- }
84
63
  throw new Error("Password required to delete account");
85
64
  }
86
65
 
87
- if (__DEV__) {
88
- console.log("[AccountDeleteHandler] Retrying with password");
89
- }
90
-
91
66
  const result = await deleteCurrentUser({
92
67
  autoReauthenticate: false,
93
68
  password,
94
69
  });
95
70
 
96
- if (__DEV__) {
97
- console.log("[AccountDeleteHandler] Password retry result:", result);
98
- }
99
-
100
71
  if (result.success) {
101
- if (__DEV__) {
102
- console.log("[AccountDeleteHandler] Delete successful after password reauth");
103
- }
104
72
  return;
105
73
  }
106
74
 
@@ -110,34 +78,15 @@ async function retryWithPassword(onPasswordRequired: () => Promise<string | null
110
78
  }
111
79
 
112
80
  async function retryWithSocialAuth(onReauthRequired: () => Promise<boolean>): Promise<void> {
113
- if (__DEV__) {
114
- console.log("[AccountDeleteHandler] Requesting social auth reauth");
115
- }
116
-
117
81
  const reauthSuccess = await onReauthRequired();
118
82
 
119
- if (__DEV__) {
120
- console.log("[AccountDeleteHandler] Reauth result:", reauthSuccess);
121
- }
122
-
123
83
  if (!reauthSuccess) {
124
84
  throw new Error("Reauthentication required to delete account");
125
85
  }
126
86
 
127
- if (__DEV__) {
128
- console.log("[AccountDeleteHandler] Retrying deletion after reauth");
129
- }
130
-
131
87
  const result = await deleteCurrentUser({ autoReauthenticate: false });
132
88
 
133
- if (__DEV__) {
134
- console.log("[AccountDeleteHandler] Reauth retry result:", result);
135
- }
136
-
137
89
  if (result.success) {
138
- if (__DEV__) {
139
- console.log("[AccountDeleteHandler] Delete successful after reauth");
140
- }
141
90
  return;
142
91
  }
143
92
 
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Auth Transition Detection Utilities
3
+ * Detects and handles authentication state transitions
4
+ */
5
+
6
+ import { useRef, useEffect } from "react";
7
+
8
+ export interface AuthTransitionState {
9
+ isAuthenticated: boolean;
10
+ isAnonymous: boolean;
11
+ isVisible: boolean;
12
+ }
13
+
14
+ export interface AuthTransitionResult {
15
+ justAuthenticated: boolean;
16
+ justConvertedFromAnonymous: boolean;
17
+ shouldClose: boolean;
18
+ }
19
+
20
+ /**
21
+ * Track auth state transitions
22
+ */
23
+ export function useAuthTransitions(
24
+ state: AuthTransitionState,
25
+ onTransition?: (result: AuthTransitionResult) => void
26
+ ): void {
27
+ const prevIsAuthenticatedRef = useRef(state.isAuthenticated);
28
+ const prevIsAnonymousRef = useRef(state.isAnonymous);
29
+ const prevIsVisibleRef = useRef(state.isVisible);
30
+
31
+ useEffect(() => {
32
+ const justAuthenticated = !prevIsAuthenticatedRef.current && state.isAuthenticated;
33
+ const justConvertedFromAnonymous =
34
+ prevIsAnonymousRef.current && !state.isAnonymous && state.isAuthenticated;
35
+ const shouldClose =
36
+ (justAuthenticated || justConvertedFromAnonymous) && state.isVisible && !state.isAnonymous;
37
+
38
+ const result: AuthTransitionResult = {
39
+ justAuthenticated,
40
+ justConvertedFromAnonymous,
41
+ shouldClose,
42
+ };
43
+
44
+ onTransition?.(result);
45
+
46
+ prevIsAuthenticatedRef.current = state.isAuthenticated;
47
+ prevIsVisibleRef.current = state.isVisible;
48
+ prevIsAnonymousRef.current = state.isAnonymous;
49
+ }, [state.isAuthenticated, state.isVisible, state.isAnonymous, onTransition]);
50
+ }
51
+
52
+ /**
53
+ * Check if should close modal after auth transition
54
+ */
55
+ export function shouldCloseAfterAuth(
56
+ justAuthenticated: boolean,
57
+ justConvertedFromAnonymous: boolean,
58
+ isVisible: boolean,
59
+ isAnonymous: boolean
60
+ ): boolean {
61
+ return (justAuthenticated || justConvertedFromAnonymous) && isVisible && !isAnonymous;
62
+ }
63
+
64
+ /**
65
+ * Execute callback with delay after auth
66
+ */
67
+ export function executeAfterAuth(
68
+ callback: () => void,
69
+ delay: number = 100
70
+ ): NodeJS.Timeout {
71
+ return setTimeout(callback, delay);
72
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Form Field State Management Utilities
3
+ * Shared utilities for form field state management across all auth forms
4
+ */
5
+
6
+ import { useCallback, useState } from "react";
7
+
8
+ export type FieldState = Record<string, string>;
9
+
10
+ export interface UseFieldStateResult<T extends FieldState> {
11
+ fields: T;
12
+ updateField: <K extends keyof T>(field: K, value: T[K]) => void;
13
+ setFields: (fields: T | ((prev: T) => T)) => void;
14
+ resetFields: (initial: T) => void;
15
+ }
16
+
17
+ /**
18
+ * Hook for managing form field state with automatic error clearing
19
+ * @param initialFields - Initial field values
20
+ * @param clearErrors - Function to clear errors when fields change
21
+ * @returns Field state and handlers
22
+ */
23
+ export function useFieldState<T extends FieldState>(
24
+ initialFields: T,
25
+ clearErrors?: (fields?: (keyof T)[]) => void
26
+ ): UseFieldStateResult<T> {
27
+ const [fields, setFields] = useState<T>(initialFields);
28
+
29
+ const updateField = useCallback(
30
+ <K extends keyof T>(field: K, value: T[K]) => {
31
+ setFields((prev) => ({ ...prev, [field]: value }));
32
+ clearErrors?.([field as string]);
33
+ },
34
+ [clearErrors]
35
+ );
36
+
37
+ const resetFields = useCallback((initial: T) => {
38
+ setFields(initial);
39
+ }, []);
40
+
41
+ return {
42
+ fields,
43
+ updateField,
44
+ setFields,
45
+ resetFields,
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Create a field change handler that updates field value and clears errors
51
+ * @param setter - State setter for the field
52
+ * @param clearErrors - Function to clear errors
53
+ * @param fieldsToClear - Fields to clear when this field changes
54
+ * @returns Field change handler
55
+ */
56
+ export function createFieldChangeHandler(
57
+ setter: (value: string) => void,
58
+ clearErrors?: () => void
59
+ ): (value: string) => void {
60
+ return (value: string) => {
61
+ setter(value);
62
+ clearErrors?.();
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Create multiple field change handlers
68
+ * @param setters - Object mapping field names to their setters
69
+ * @param clearErrors - Function to clear errors
70
+ * @returns Object mapping field names to change handlers
71
+ */
72
+ export function createFieldChangeHandlers<T extends Record<string, (value: string) => void>>(
73
+ setters: T,
74
+ clearErrors?: () => void
75
+ ): T {
76
+ return Object.fromEntries(
77
+ Object.entries(setters).map(([key, setter]) => [
78
+ key,
79
+ createFieldChangeHandler(setter, clearErrors),
80
+ ])
81
+ ) as T;
82
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Form Validation Utilities
3
+ * Shared validation logic for all auth forms
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import {
8
+ validateEmail,
9
+ validatePasswordForLogin,
10
+ validatePasswordForRegister,
11
+ validatePasswordConfirmation,
12
+ } from "../../../infrastructure/utils/AuthValidation";
13
+ import type { PasswordConfig } from "../../../domain/value-objects/AuthConfig";
14
+
15
+ export interface FormValidationError {
16
+ field: string;
17
+ message: string;
18
+ }
19
+
20
+ export interface FormValidationResult {
21
+ isValid: boolean;
22
+ errors: FormValidationError[];
23
+ }
24
+
25
+ export interface LoginFormValues {
26
+ email: string;
27
+ password: string;
28
+ }
29
+
30
+ export interface RegisterFormValues {
31
+ displayName?: string;
32
+ email: string;
33
+ password: string;
34
+ confirmPassword: string;
35
+ }
36
+
37
+ export interface ProfileFormValues {
38
+ displayName: string;
39
+ email: string;
40
+ }
41
+
42
+ /**
43
+ * Validate login form fields
44
+ * @param values - Form values to validate
45
+ * @param getErrorMessage - Function to get localized error messages
46
+ * @returns Validation result
47
+ */
48
+ export function validateLoginForm(
49
+ values: LoginFormValues,
50
+ getErrorMessage: (key: string) => string
51
+ ): FormValidationResult {
52
+ const errors: FormValidationError[] = [];
53
+
54
+ const emailResult = validateEmail(values.email.trim());
55
+ if (!emailResult.isValid && emailResult.error) {
56
+ errors.push({ field: "email", message: getErrorMessage(emailResult.error) });
57
+ }
58
+
59
+ const passwordResult = validatePasswordForLogin(values.password);
60
+ if (!passwordResult.isValid && passwordResult.error) {
61
+ errors.push({ field: "password", message: getErrorMessage(passwordResult.error) });
62
+ }
63
+
64
+ return {
65
+ isValid: errors.length === 0,
66
+ errors,
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Validate register form fields
72
+ * @param values - Form values to validate
73
+ * @param getErrorMessage - Function to get localized error messages
74
+ * @param passwordConfig - Password configuration
75
+ * @returns Validation result
76
+ */
77
+ export function validateRegisterForm(
78
+ values: RegisterFormValues,
79
+ getErrorMessage: (key: string) => string,
80
+ passwordConfig: PasswordConfig
81
+ ): FormValidationResult {
82
+ const errors: FormValidationError[] = [];
83
+
84
+ const emailResult = validateEmail(values.email.trim());
85
+ if (!emailResult.isValid && emailResult.error) {
86
+ errors.push({ field: "email", message: getErrorMessage(emailResult.error) });
87
+ }
88
+
89
+ const passwordResult = validatePasswordForRegister(values.password, passwordConfig);
90
+ if (!passwordResult.isValid && passwordResult.error) {
91
+ errors.push({ field: "password", message: getErrorMessage(passwordResult.error) });
92
+ }
93
+
94
+ const confirmResult = validatePasswordConfirmation(values.password, values.confirmPassword);
95
+ if (!confirmResult.isValid && confirmResult.error) {
96
+ errors.push({ field: "confirmPassword", message: getErrorMessage(confirmResult.error) });
97
+ }
98
+
99
+ return {
100
+ isValid: errors.length === 0,
101
+ errors,
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Validate profile form fields
107
+ * @param values - Form values to validate
108
+ * @returns Validation result
109
+ */
110
+ export function validateProfileForm(values: ProfileFormValues): FormValidationResult {
111
+ const errors: FormValidationError[] = [];
112
+
113
+ if (!values.displayName.trim()) {
114
+ errors.push({ field: "displayName", message: "Display name is required" });
115
+ }
116
+
117
+ if (values.email) {
118
+ const emailResult = validateEmail(values.email);
119
+ if (!emailResult.isValid && emailResult.error) {
120
+ errors.push({ field: "email", message: emailResult.error });
121
+ }
122
+ }
123
+
124
+ return {
125
+ isValid: errors.length === 0,
126
+ errors,
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Convert validation errors to field error object
132
+ * @param errors - Validation errors
133
+ * @returns Object mapping field names to error messages
134
+ */
135
+ export function errorsToFieldErrors(
136
+ errors: FormValidationError[]
137
+ ): Record<string, string> {
138
+ const result: Record<string, string> = {};
139
+ for (const error of errors) {
140
+ result[error.field] = error.message;
141
+ }
142
+ return result;
143
+ }
144
+
145
+ /**
146
+ * Hook for form validation with error message resolution
147
+ * @param getErrorMessage - Function to get localized error messages
148
+ * @returns Validation functions
149
+ */
150
+ export function useFormValidation(getErrorMessage: (key: string) => string) {
151
+ const validateLogin = useCallback(
152
+ (values: LoginFormValues) => validateLoginForm(values, getErrorMessage),
153
+ [getErrorMessage]
154
+ );
155
+
156
+ const validateRegister = useCallback(
157
+ (values: RegisterFormValues, passwordConfig: PasswordConfig) =>
158
+ validateRegisterForm(values, getErrorMessage, passwordConfig),
159
+ [getErrorMessage]
160
+ );
161
+
162
+ const validateProfile = useCallback(
163
+ (values: ProfileFormValues) => validateProfileForm(values),
164
+ []
165
+ );
166
+
167
+ return {
168
+ validateLogin,
169
+ validateRegister,
170
+ validateProfile,
171
+ errorsToFieldErrors,
172
+ };
173
+ }