@umituz/react-native-auth 4.3.62 → 4.3.63

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-auth",
3
- "version": "4.3.62",
3
+ "version": "4.3.63",
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",
package/src/index.ts CHANGED
@@ -39,6 +39,40 @@ export { isEmpty, isEmptyEmail, isEmptyPassword, isEmptyName, isNotEmpty, hasCon
39
39
  export { safeCallback, safeCallbackSync } from './infrastructure/utils/safeCallback';
40
40
 
41
41
 
42
+ // =============================================================================
43
+ // CALCULATOR UTILITIES
44
+ // =============================================================================
45
+
46
+ export {
47
+ // Auth State Calculator
48
+ calculateUserId,
49
+ calculateHasFirebaseUser,
50
+ calculateIsAnonymous,
51
+ calculateIsAuthenticated,
52
+ calculateUserType,
53
+ calculateIsAuthReady,
54
+ calculateDerivedAuthState,
55
+ // Form Error Collection
56
+ collectFieldErrors,
57
+ extractFieldError,
58
+ hasFieldErrors,
59
+ getFirstErrorMessage,
60
+ // User Profile Calculator
61
+ calculateUserProfileDisplay,
62
+ calculateDisplayName,
63
+ hasUserAvatar,
64
+ getAvatarUrl,
65
+ // Password Strength Calculator
66
+ calculatePasswordRequirements,
67
+ calculatePasswordsMatch,
68
+ calculateConfirmationError,
69
+ calculatePasswordValidity,
70
+ calculatePasswordValidation,
71
+ hasMinLength,
72
+ calculatePasswordStrength,
73
+ } from './infrastructure/utils/calculators';
74
+
75
+
42
76
  // =============================================================================
43
77
  // PRESENTATION LAYER - Hooks
44
78
  // =============================================================================
@@ -119,6 +153,7 @@ export {
119
153
  selectUserType,
120
154
  selectIsAuthReady,
121
155
  selectFirebaseUserId,
156
+ selectAuthState,
122
157
  } from './presentation/stores/auth.selectors';
123
158
 
124
159
  // =============================================================================
@@ -58,11 +58,12 @@ class AuthEventService {
58
58
  }
59
59
 
60
60
  addEventListener(event: string, listener: AuthEventListener): () => void {
61
- if (!this.listeners.has(event)) {
62
- this.listeners.set(event, []);
61
+ let eventListeners = this.listeners.get(event);
62
+ if (!eventListeners) {
63
+ eventListeners = [];
64
+ this.listeners.set(event, eventListeners);
63
65
  }
64
66
 
65
- const eventListeners = this.listeners.get(event)!;
66
67
  eventListeners.push(listener);
67
68
 
68
69
  // Return cleanup function
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Auth State Calculator
3
+ * Pure utility functions for deriving auth state from Firebase user
4
+ * These calculations are used in selectors and can be tested independently
5
+ */
6
+
7
+ import type { AuthUser } from "../../../domain/entities/AuthUser";
8
+ import type { UserType } from "../../../types/auth-store.types";
9
+
10
+ interface FirebaseUserLike {
11
+ uid: string;
12
+ isAnonymous: boolean;
13
+ }
14
+
15
+ interface AuthStateInput {
16
+ firebaseUser: FirebaseUserLike | null;
17
+ user: AuthUser | null;
18
+ loading: boolean;
19
+ initialized: boolean;
20
+ error: string | null;
21
+ }
22
+
23
+ /**
24
+ * Calculate user ID from Firebase user
25
+ */
26
+ export function calculateUserId(firebaseUser: FirebaseUserLike | null): string | null {
27
+ return firebaseUser?.uid ?? null;
28
+ }
29
+
30
+ /**
31
+ * Calculate if user has Firebase account
32
+ */
33
+ export function calculateHasFirebaseUser(firebaseUser: FirebaseUserLike | null): boolean {
34
+ return !!firebaseUser;
35
+ }
36
+
37
+ /**
38
+ * Calculate if user is anonymous
39
+ */
40
+ export function calculateIsAnonymous(firebaseUser: FirebaseUserLike | null): boolean {
41
+ return firebaseUser?.isAnonymous ?? false;
42
+ }
43
+
44
+ /**
45
+ * Calculate if user is authenticated (has Firebase user, not anonymous)
46
+ */
47
+ export function calculateIsAuthenticated(firebaseUser: FirebaseUserLike | null): boolean {
48
+ const hasFirebaseUser = !!firebaseUser;
49
+ const isNotAnonymous = !firebaseUser?.isAnonymous;
50
+ return hasFirebaseUser && isNotAnonymous;
51
+ }
52
+
53
+ /**
54
+ * Calculate user type from Firebase user
55
+ */
56
+ export function calculateUserType(firebaseUser: FirebaseUserLike | null): UserType {
57
+ if (!firebaseUser) {
58
+ return "none";
59
+ }
60
+ return firebaseUser.isAnonymous ? "anonymous" : "authenticated";
61
+ }
62
+
63
+ /**
64
+ * Calculate if auth is ready (initialized and not loading)
65
+ */
66
+ export function calculateIsAuthReady(initialized: boolean, loading: boolean): boolean {
67
+ return initialized && !loading;
68
+ }
69
+
70
+ /**
71
+ * Calculate all derived auth state at once
72
+ * More efficient than calling individual functions multiple times
73
+ */
74
+ export function calculateDerivedAuthState(input: AuthStateInput): {
75
+ userId: string | null;
76
+ hasFirebaseUser: boolean;
77
+ isAnonymous: boolean;
78
+ isAuthenticated: boolean;
79
+ userType: UserType;
80
+ isAuthReady: boolean;
81
+ } {
82
+ const { firebaseUser, initialized, loading } = input;
83
+
84
+ return {
85
+ userId: calculateUserId(firebaseUser),
86
+ hasFirebaseUser: calculateHasFirebaseUser(firebaseUser),
87
+ isAnonymous: calculateIsAnonymous(firebaseUser),
88
+ isAuthenticated: calculateIsAuthenticated(firebaseUser),
89
+ userType: calculateUserType(firebaseUser),
90
+ isAuthReady: calculateIsAuthReady(initialized, loading),
91
+ };
92
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Form Error Collection Utility
3
+ * Pure utility functions for collecting and extracting form errors
4
+ * Separates error collection logic from hooks for better testability
5
+ */
6
+
7
+ import type { FormValidationError } from "../../../presentation/utils/form/validation/formValidation.types";
8
+
9
+ /**
10
+ * Collect field errors from validation result
11
+ * Extracts error messages for specific fields
12
+ */
13
+ export function collectFieldErrors(
14
+ errors: FormValidationError[],
15
+ fields: string[]
16
+ ): Record<string, string | null> {
17
+ const result: Record<string, string | null> = {};
18
+
19
+ for (const field of fields) {
20
+ const fieldError = errors.find((e) => e.field === field);
21
+ result[field] = fieldError?.message ?? null;
22
+ }
23
+
24
+ return result;
25
+ }
26
+
27
+ /**
28
+ * Extract single field error from validation result
29
+ */
30
+ export function extractFieldError(
31
+ errors: FormValidationError[],
32
+ field: string
33
+ ): string | null {
34
+ const fieldError = errors.find((e) => e.field === field);
35
+ return fieldError?.message ?? null;
36
+ }
37
+
38
+ /**
39
+ * Check if validation has errors for specific fields
40
+ */
41
+ export function hasFieldErrors(
42
+ errors: FormValidationError[],
43
+ fields: string[]
44
+ ): boolean {
45
+ return errors.some((error) => fields.includes(error.field));
46
+ }
47
+
48
+ /**
49
+ * Get first error message from validation result
50
+ */
51
+ export function getFirstErrorMessage(errors: FormValidationError[]): string | null {
52
+ if (errors.length === 0) {
53
+ return null;
54
+ }
55
+ return errors[0].message ?? null;
56
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Calculator Utilities Index
3
+ * Centralized exports for all calculator utilities
4
+ */
5
+
6
+ // Auth State Calculator
7
+ export {
8
+ calculateUserId,
9
+ calculateHasFirebaseUser,
10
+ calculateIsAnonymous,
11
+ calculateIsAuthenticated,
12
+ calculateUserType,
13
+ calculateIsAuthReady,
14
+ calculateDerivedAuthState,
15
+ } from "./authStateCalculator";
16
+
17
+ // Form Error Collection
18
+ export {
19
+ collectFieldErrors,
20
+ extractFieldError,
21
+ hasFieldErrors,
22
+ getFirstErrorMessage,
23
+ } from "./formErrorCollection";
24
+
25
+ // User Profile Calculator
26
+ export {
27
+ calculateUserProfileDisplay,
28
+ calculateDisplayName,
29
+ hasUserAvatar,
30
+ getAvatarUrl,
31
+ } from "./userProfileCalculator";
32
+
33
+ // Password Strength Calculator
34
+ export {
35
+ calculatePasswordRequirements,
36
+ calculatePasswordsMatch,
37
+ calculateConfirmationError,
38
+ calculatePasswordValidity,
39
+ calculatePasswordValidation,
40
+ hasMinLength,
41
+ calculatePasswordStrength,
42
+ } from "./passwordStrengthCalculator";
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Password Strength Calculator Utility
3
+ * Pure utility functions for password validation and strength calculation
4
+ * Separates password logic from hooks for better testability
5
+ */
6
+
7
+ import {
8
+ validatePasswordForRegister,
9
+ validatePasswordConfirmation,
10
+ } from "../AuthValidation";
11
+ import type { PasswordConfig } from "../../../domain/value-objects/AuthConfig";
12
+ import type { PasswordRequirements } from "../validation/types";
13
+
14
+ interface PasswordValidationInput {
15
+ password: string;
16
+ confirmPassword: string;
17
+ config?: PasswordConfig;
18
+ }
19
+
20
+ interface PasswordValidationResult {
21
+ requirements: PasswordRequirements;
22
+ passwordsMatch: boolean;
23
+ isValid: boolean;
24
+ confirmationError: string | null;
25
+ }
26
+
27
+ /**
28
+ * Calculate password requirements from validation result
29
+ */
30
+ export function calculatePasswordRequirements(
31
+ password: string,
32
+ config?: PasswordConfig
33
+ ): PasswordRequirements {
34
+ if (!password || !config) {
35
+ return { hasMinLength: false };
36
+ }
37
+
38
+ const result = validatePasswordForRegister(password, config);
39
+ return result.requirements;
40
+ }
41
+
42
+ /**
43
+ * Calculate if passwords match
44
+ * Explicitly converts to boolean to avoid type issues
45
+ */
46
+ export function calculatePasswordsMatch(
47
+ password: string,
48
+ confirmPassword: string
49
+ ): boolean {
50
+ return !!(password && confirmPassword && password === confirmPassword);
51
+ }
52
+
53
+ /**
54
+ * Calculate password confirmation error
55
+ */
56
+ export function calculateConfirmationError(
57
+ password: string,
58
+ confirmPassword: string
59
+ ): string | null {
60
+ if (!confirmPassword) {
61
+ return null;
62
+ }
63
+
64
+ const result = validatePasswordConfirmation(password, confirmPassword);
65
+ return result.error ?? null;
66
+ }
67
+
68
+ /**
69
+ * Calculate overall password validity
70
+ */
71
+ export function calculatePasswordValidity(
72
+ requirements: PasswordRequirements,
73
+ passwordsMatch: boolean
74
+ ): boolean {
75
+ return requirements.hasMinLength && passwordsMatch;
76
+ }
77
+
78
+ /**
79
+ * Calculate all password validation state at once
80
+ * More efficient than calling individual functions
81
+ */
82
+ export function calculatePasswordValidation(
83
+ input: PasswordValidationInput
84
+ ): PasswordValidationResult {
85
+ const { password, confirmPassword, config } = input;
86
+
87
+ // Calculate password requirements
88
+ const requirements = calculatePasswordRequirements(password, config);
89
+
90
+ // Calculate if passwords match
91
+ const passwordsMatch = calculatePasswordsMatch(password, confirmPassword);
92
+
93
+ // Calculate confirmation error
94
+ const confirmationError = calculateConfirmationError(password, confirmPassword);
95
+
96
+ // Calculate overall validity
97
+ const isValid = calculatePasswordValidity(requirements, passwordsMatch);
98
+
99
+ return {
100
+ requirements,
101
+ passwordsMatch,
102
+ isValid,
103
+ confirmationError,
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Quick check if password meets minimum length requirement
109
+ */
110
+ export function hasMinLength(password: string, minLength: number): boolean {
111
+ return password.length >= minLength;
112
+ }
113
+
114
+ /**
115
+ * Calculate password strength score (0-100)
116
+ * Can be extended for more sophisticated strength calculation
117
+ */
118
+ export function calculatePasswordStrength(
119
+ password: string,
120
+ requirements: PasswordRequirements
121
+ ): number {
122
+ if (!password) return 0;
123
+
124
+ let score = 0;
125
+
126
+ // Base score for meeting minimum length
127
+ if (requirements.hasMinLength) {
128
+ score += 40;
129
+ }
130
+
131
+ // Additional points for length
132
+ if (password.length >= 8) score += 20;
133
+ if (password.length >= 12) score += 20;
134
+
135
+ // Additional points for variety (can be extended)
136
+ const hasLower = /[a-z]/.test(password);
137
+ const hasUpper = /[A-Z]/.test(password);
138
+ const hasNumber = /[0-9]/.test(password);
139
+ const hasSpecial = /[^a-zA-Z0-9]/.test(password);
140
+
141
+ const varietyCount = [hasLower, hasUpper, hasNumber, hasSpecial].filter(Boolean).length;
142
+ score += varietyCount * 5;
143
+
144
+ return Math.min(score, 100);
145
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * User Profile Calculator Utility
3
+ * Pure utility functions for calculating user profile display data
4
+ * Separates profile calculation logic from hooks for better testability
5
+ */
6
+
7
+ import type { AuthUser } from "../../../domain/entities/AuthUser";
8
+
9
+ interface UserProfileDisplayParams {
10
+ anonymousDisplayName?: string;
11
+ accountRoute?: string;
12
+ }
13
+
14
+ interface UserProfileDisplayResult {
15
+ displayName: string;
16
+ userId: string;
17
+ isAnonymous: boolean;
18
+ avatarUrl?: string;
19
+ accountSettingsRoute?: string;
20
+ }
21
+
22
+ /**
23
+ * Calculate user profile display data
24
+ * Handles anonymous vs authenticated user display logic
25
+ */
26
+ export function calculateUserProfileDisplay(
27
+ user: AuthUser,
28
+ params: UserProfileDisplayParams = {}
29
+ ): UserProfileDisplayResult {
30
+ const { anonymousDisplayName = "Anonymous User", accountRoute } = params;
31
+
32
+ if (user.isAnonymous) {
33
+ return {
34
+ displayName: anonymousDisplayName,
35
+ userId: user.uid,
36
+ isAnonymous: true,
37
+ accountSettingsRoute: accountRoute,
38
+ };
39
+ }
40
+
41
+ return {
42
+ displayName: user.displayName || user.email || anonymousDisplayName,
43
+ userId: user.uid,
44
+ isAnonymous: false,
45
+ avatarUrl: user.photoURL || undefined,
46
+ accountSettingsRoute: accountRoute,
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Get display name for user
52
+ * Extracts display name calculation for reusability
53
+ */
54
+ export function calculateDisplayName(
55
+ user: AuthUser,
56
+ anonymousDisplayName: string = "Anonymous User"
57
+ ): string {
58
+ if (user.isAnonymous) {
59
+ return anonymousDisplayName;
60
+ }
61
+
62
+ return user.displayName || user.email || anonymousDisplayName;
63
+ }
64
+
65
+ /**
66
+ * Check if user has avatar
67
+ */
68
+ export function hasUserAvatar(user: AuthUser): boolean {
69
+ return !!user.photoURL;
70
+ }
71
+
72
+ /**
73
+ * Get avatar URL if available
74
+ */
75
+ export function getAvatarUrl(user: AuthUser): string | undefined {
76
+ return user.photoURL || undefined;
77
+ }
@@ -1,4 +1,4 @@
1
- import React, { useRef } from "react";
1
+ import React, { useRef, memo } from "react";
2
2
  import { StyleSheet, TextInput } from "react-native";
3
3
  import { AtomicButton } from "@umituz/react-native-design-system/atoms";
4
4
  import { useLoginForm } from "../hooks/useLoginForm";
@@ -22,7 +22,7 @@ interface LoginFormProps {
22
22
  onNavigateToRegister: () => void;
23
23
  }
24
24
 
25
- export const LoginForm: React.FC<LoginFormProps> = ({
25
+ export const LoginForm = memo<LoginFormProps>(({
26
26
  translations,
27
27
  onNavigateToRegister,
28
28
  }) => {
@@ -84,7 +84,7 @@ export const LoginForm: React.FC<LoginFormProps> = ({
84
84
  />
85
85
  </>
86
86
  );
87
- };
87
+ });
88
88
 
89
89
  const styles = StyleSheet.create({
90
90
  signInButton: {
@@ -92,3 +92,5 @@ const styles = StyleSheet.create({
92
92
  marginBottom: 16,
93
93
  },
94
94
  });
95
+
96
+ LoginForm.displayName = 'LoginForm';
@@ -1,4 +1,4 @@
1
- import React, { useRef } from "react";
1
+ import React, { useRef, memo } from "react";
2
2
  import { StyleSheet, TextInput } from "react-native";
3
3
  import { AtomicButton } from "@umituz/react-native-design-system/atoms";
4
4
  import { useRegisterForm } from "../hooks/useRegisterForm";
@@ -38,7 +38,7 @@ interface RegisterFormProps {
38
38
  onPrivacyPress?: () => void;
39
39
  }
40
40
 
41
- export const RegisterForm: React.FC<RegisterFormProps> = ({
41
+ export const RegisterForm = memo<RegisterFormProps>(({
42
42
  translations,
43
43
  onNavigateToLogin,
44
44
  termsUrl,
@@ -149,10 +149,12 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
149
149
  />
150
150
  </>
151
151
  );
152
- };
152
+ });
153
153
 
154
154
  const styles = StyleSheet.create({
155
155
  passwordInput: { marginBottom: 4 },
156
156
  confirmPasswordInput: { marginBottom: 4 },
157
157
  signUpButton: { minHeight: 52, marginBottom: 16, marginTop: 8 },
158
158
  });
159
+
160
+ RegisterForm.displayName = 'RegisterForm';
@@ -1,22 +1,15 @@
1
1
  /**
2
2
  * useAuth Hook
3
3
  * React hook for authentication state management
4
+ * PERFORMANCE: Uses single batch selector to minimize re-renders
4
5
  */
5
6
 
6
7
  import { useCallback } from "react";
7
8
  import { useAuthStore } from "../stores/authStore";
8
9
  import {
9
- selectUser,
10
- selectLoading,
11
- selectError,
10
+ selectAuthState,
12
11
  selectSetLoading,
13
12
  selectSetError,
14
- selectIsAuthenticated,
15
- selectHasFirebaseUser,
16
- selectUserId,
17
- selectUserType,
18
- selectIsAnonymous,
19
- selectIsAuthReady,
20
13
  } from "../stores/auth.selectors";
21
14
  import type { UserType } from "../../types/auth-store.types";
22
15
  import {
@@ -45,15 +38,9 @@ export interface UseAuthResult {
45
38
  }
46
39
 
47
40
  export function useAuth(): UseAuthResult {
48
- const user = useAuthStore(selectUser);
49
- const loading = useAuthStore(selectLoading);
50
- const error = useAuthStore(selectError);
51
- const isAuthenticated = useAuthStore(selectIsAuthenticated);
52
- const hasFirebaseUser = useAuthStore(selectHasFirebaseUser);
53
- const userId = useAuthStore(selectUserId);
54
- const userType = useAuthStore(selectUserType);
55
- const isAnonymous = useAuthStore(selectIsAnonymous);
56
- const isAuthReady = useAuthStore(selectIsAuthReady);
41
+ // PERFORMANCE: Single batch selector instead of 10 separate selectors
42
+ // This reduces re-renders from 10x to 1x when auth state changes
43
+ const authState = useAuthStore(selectAuthState);
57
44
  const setLoading = useAuthStore(selectSetLoading);
58
45
  const setError = useAuthStore(selectSetError);
59
46
 
@@ -124,7 +111,7 @@ export function useAuth(): UseAuthResult {
124
111
  }, [setLoading, setError, anonymousModeMutation.mutateAsync]);
125
112
 
126
113
  return {
127
- user, userId, userType, loading, isAuthReady, isAnonymous, isAuthenticated, hasFirebaseUser, error,
114
+ ...authState,
128
115
  signUp, signIn, signOut, continueAnonymously, setError,
129
116
  };
130
117
  }
@@ -38,9 +38,19 @@ export function useAuthBottomSheet(params: UseAuthBottomSheetParams = {}) {
38
38
  const { signInWithApple, appleAvailable } = useAppleAuth();
39
39
 
40
40
  // Determine enabled providers
41
+ // PERFORMANCE: Memoize to prevent recalculation when config hasn't changed
41
42
  const providers = useMemo<SocialAuthProvider[]>(() => {
42
43
  return determineEnabledProviders(socialConfig, appleAvailable, googleConfigured);
43
- }, [socialConfig, appleAvailable, googleConfigured]);
44
+ }, [
45
+ // Use deep comparison for socialConfig to avoid unnecessary recalculation
46
+ // when parent passes a new object reference with same values
47
+ socialConfig?.google?.iosClientId,
48
+ socialConfig?.google?.webClientId,
49
+ socialConfig?.google?.androidClientId,
50
+ socialConfig?.apple?.enabled,
51
+ appleAvailable,
52
+ googleConfigured,
53
+ ]);
44
54
 
45
55
  // Social auth loading states
46
56
  const [googleLoading, setGoogleLoading] = useState(false);
@@ -56,7 +56,7 @@ export const useAuthHandlers = (appInfo: AuthHandlersAppInfo, translations?: Aut
56
56
  }
57
57
  await Linking.openURL(url);
58
58
  } catch (error) {
59
- if (typeof __DEV__ !== "undefined" && __DEV__) {
59
+ if (__DEV__) {
60
60
  console.error("[useAuthHandlers] Failed to open app store:", error);
61
61
  }
62
62
  Alert.alert(translations?.common || "", translations?.failedToOpenAppStore || "");
@@ -67,7 +67,7 @@ export const useAuthHandlers = (appInfo: AuthHandlersAppInfo, translations?: Aut
67
67
  try {
68
68
  await signOut();
69
69
  } catch (error) {
70
- if (typeof __DEV__ !== "undefined" && __DEV__) {
70
+ if (__DEV__) {
71
71
  console.error("[useAuthHandlers] Sign out failed:", error);
72
72
  }
73
73
  AlertService.createErrorAlert(
@@ -6,6 +6,7 @@ import { useFormFields } from "../utils/form/useFormField.hook";
6
6
  import { sanitizeEmail } from "../../infrastructure/utils/validation/sanitization";
7
7
  import { useAuthErrorHandler } from "./useAuthErrorHandler";
8
8
  import { useLocalError } from "./useLocalError";
9
+ import { extractFieldError } from "../../infrastructure/utils/calculators/formErrorCollection";
9
10
 
10
11
  interface LoginFormTranslations {
11
12
  successTitle: string;
@@ -44,7 +45,7 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
44
45
  setEmailError(null);
45
46
  setPasswordError(null);
46
47
  setLocalError(null);
47
- }, [setLocalError]);
48
+ }, [setLocalError, setEmailError, setPasswordError]);
48
49
 
49
50
  const { fields, updateField } = useFormFields(
50
51
  { email: "", password: "" },
@@ -79,11 +80,9 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
79
80
  );
80
81
 
81
82
  if (!validation.isValid) {
82
- // Collect errors first to avoid potential state update batching issues
83
- const emailErrorMsg = validation.errors.find(e => e.field === "email")?.message || null;
84
- const passwordErrorMsg = validation.errors.find(e => e.field === "password")?.message || null;
85
- setEmailError(emailErrorMsg);
86
- setPasswordError(passwordErrorMsg);
83
+ // Use utility to collect field errors
84
+ setEmailError(extractFieldError(validation.errors, "email"));
85
+ setPasswordError(extractFieldError(validation.errors, "password"));
87
86
  return;
88
87
  }
89
88
 
@@ -32,7 +32,7 @@ export function useRegisterForm(config?: UseRegisterFormConfig): UseRegisterForm
32
32
  const clearFormErrors = useCallback(() => {
33
33
  setLocalError(null);
34
34
  setFieldErrors({});
35
- }, []);
35
+ }, [setLocalError, setFieldErrors]);
36
36
 
37
37
  const { fields, updateField } = useFormFields(
38
38
  {
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * useUserProfile Hook
3
3
  * Returns profile data for display in settings or profile screens
4
+ * Uses userProfileCalculator utility for calculation logic
4
5
  */
5
6
 
6
7
  import { useMemo } from "react";
7
8
  import { useAuth } from "./useAuth";
9
+ import { calculateUserProfileDisplay } from "../../infrastructure/utils/calculators/userProfileCalculator";
8
10
 
9
11
  export interface UserProfileData {
10
12
  displayName?: string;
@@ -24,29 +26,11 @@ export const useUserProfile = (
24
26
  params?: UseUserProfileParams,
25
27
  ): UserProfileData | undefined => {
26
28
  const { user } = useAuth();
27
- const anonymousName = params?.anonymousDisplayName ?? "Anonymous User";
28
- const accountRoute = params?.accountRoute;
29
29
 
30
30
  return useMemo(() => {
31
31
  if (!user) return undefined;
32
32
 
33
- const isAnonymous = user.isAnonymous || false;
34
-
35
- if (isAnonymous) {
36
- return {
37
- displayName: anonymousName,
38
- userId: user.uid,
39
- isAnonymous: true,
40
- accountSettingsRoute: accountRoute,
41
- };
42
- }
43
-
44
- return {
45
- accountSettingsRoute: accountRoute,
46
- displayName: user.displayName || user.email || anonymousName,
47
- userId: user.uid,
48
- isAnonymous: false,
49
- avatarUrl: user.photoURL || undefined,
50
- };
51
- }, [user, anonymousName, accountRoute]);
33
+ // Delegate to utility function
34
+ return calculateUserProfileDisplay(user, params);
35
+ }, [user, params]);
52
36
  };
@@ -1,10 +1,20 @@
1
1
  /**
2
2
  * Auth Store Selectors
3
3
  * Pure functions for deriving state from auth store
4
+ * Uses authStateCalculator for derived state calculations
4
5
  */
5
6
 
6
7
  import type { AuthState, AuthActions, UserType } from "../../types/auth-store.types";
7
8
  import type { AuthUser } from "../../domain/entities/AuthUser";
9
+ import {
10
+ calculateUserId,
11
+ calculateHasFirebaseUser,
12
+ calculateIsAnonymous,
13
+ calculateIsAuthenticated,
14
+ calculateUserType,
15
+ calculateIsAuthReady,
16
+ calculateDerivedAuthState,
17
+ } from "../../infrastructure/utils/calculators/authStateCalculator";
8
18
 
9
19
  // Combined store type for selectors
10
20
  type AuthStore = AuthState & AuthActions;
@@ -63,13 +73,15 @@ export const selectShowAuthModal = (state: { showAuthModal: (callback?: () => vo
63
73
  // =============================================================================
64
74
  // DERIVED SELECTORS
65
75
  // =============================================================================
76
+ // Note: These selectors delegate to authStateCalculator utilities
77
+ // for better separation of concerns and testability
66
78
 
67
79
  /**
68
80
  * Get current user ID
69
81
  * Uses firebaseUser as single source of truth
70
82
  */
71
83
  export const selectUserId = (state: AuthStore): string | null => {
72
- return state.firebaseUser?.uid ?? null;
84
+ return calculateUserId(state.firebaseUser);
73
85
  };
74
86
 
75
87
  /**
@@ -77,13 +89,11 @@ export const selectUserId = (state: AuthStore): string | null => {
77
89
  * Uses firebaseUser as single source of truth
78
90
  */
79
91
  export const selectIsAuthenticated = (state: AuthStore): boolean => {
80
- const hasFirebaseUser = !!state.firebaseUser;
81
- const isNotAnonymous = !state.firebaseUser?.isAnonymous;
82
- return hasFirebaseUser && isNotAnonymous;
92
+ return calculateIsAuthenticated(state.firebaseUser);
83
93
  };
84
94
 
85
95
  export const selectHasFirebaseUser = (state: AuthStore): boolean => {
86
- return !!state.firebaseUser;
96
+ return calculateHasFirebaseUser(state.firebaseUser);
87
97
  };
88
98
 
89
99
  /**
@@ -91,7 +101,7 @@ export const selectHasFirebaseUser = (state: AuthStore): boolean => {
91
101
  * Uses firebaseUser as single source of truth
92
102
  */
93
103
  export const selectIsAnonymous = (state: AuthStore): boolean => {
94
- return state.firebaseUser?.isAnonymous ?? false;
104
+ return calculateIsAnonymous(state.firebaseUser);
95
105
  };
96
106
 
97
107
  /**
@@ -99,17 +109,45 @@ export const selectIsAnonymous = (state: AuthStore): boolean => {
99
109
  * Derived from firebaseUser state
100
110
  */
101
111
  export const selectUserType = (state: AuthStore): UserType => {
102
- if (!state.firebaseUser) {
103
- return "none";
104
- }
105
-
106
- return state.firebaseUser.isAnonymous ? "anonymous" : "authenticated";
112
+ return calculateUserType(state.firebaseUser);
107
113
  };
108
114
 
109
115
  /**
110
116
  * Check if auth is ready (initialized and not loading)
111
117
  */
112
118
  export const selectIsAuthReady = (state: AuthStore): boolean => {
113
- return state.initialized && !state.loading;
119
+ return calculateIsAuthReady(state.initialized, state.loading);
120
+ };
121
+
122
+ /**
123
+ * Batch selector - get all auth state at once to minimize re-renders
124
+ * More efficient than calling selectors individually
125
+ */
126
+ export const selectAuthState = (state: AuthStore): {
127
+ user: AuthUser | null;
128
+ userId: string | null;
129
+ userType: UserType;
130
+ loading: boolean;
131
+ isAuthReady: boolean;
132
+ isAnonymous: boolean;
133
+ isAuthenticated: boolean;
134
+ hasFirebaseUser: boolean;
135
+ error: string | null;
136
+ } => {
137
+ // Use calculateDerivedAuthState for batch calculation efficiency
138
+ const derivedState = calculateDerivedAuthState({
139
+ firebaseUser: state.firebaseUser,
140
+ user: state.user,
141
+ loading: state.loading,
142
+ initialized: state.initialized,
143
+ error: state.error,
144
+ });
145
+
146
+ return {
147
+ user: selectUser(state),
148
+ loading: selectLoading(state),
149
+ error: selectError(state),
150
+ ...derivedState,
151
+ };
114
152
  };
115
153
 
@@ -28,9 +28,12 @@ export const useAuthStore = createStore<AuthState, AuthActions>({
28
28
  removeItem: (name) => storageService.removeItem(name),
29
29
  },
30
30
  version: 2,
31
+ // PERFORMANCE: Only persist essential flags
32
+ // Firebase handles user persistence, we only need anonymous mode and initialized state
31
33
  partialize: (state) => ({
32
34
  isAnonymous: state.isAnonymous,
33
35
  initialized: state.initialized,
36
+ loading: false, // Always restore as false to prevent loading state on app restart
34
37
  }),
35
38
  migrate: (persistedState: unknown) => {
36
39
  const state = (persistedState && typeof persistedState === "object" ? persistedState : {}) as Partial<AuthState>;
@@ -1,15 +1,13 @@
1
1
  /**
2
2
  * Password Validation Hook
3
3
  * Provides reusable password validation logic with requirements tracking
4
+ * PERFORMANCE: Single useMemo using passwordStrengthCalculator utility
4
5
  */
5
6
 
6
7
  import { useMemo } from "react";
7
- import {
8
- validatePasswordForRegister,
9
- validatePasswordConfirmation,
10
- } from "../../../infrastructure/utils/AuthValidation";
11
8
  import type { PasswordRequirements } from "../../../infrastructure/utils/validation/types";
12
9
  import type { PasswordConfig } from "../../../domain/value-objects/AuthConfig";
10
+ import { calculatePasswordValidation } from "../../../infrastructure/utils/calculators/passwordStrengthCalculator";
13
11
 
14
12
  interface UsePasswordValidationResult {
15
13
  passwordRequirements: PasswordRequirements;
@@ -34,40 +32,23 @@ export function usePasswordValidation(
34
32
  confirmPassword: string,
35
33
  options?: UsePasswordValidationOptions
36
34
  ): UsePasswordValidationResult {
37
- const config = options?.passwordConfig;
38
-
39
- const passwordRequirements = useMemo((): PasswordRequirements => {
40
- if (!password || !config) {
41
- return { hasMinLength: false };
42
- }
43
- const result = validatePasswordForRegister(password, config);
44
- return result.requirements;
45
- }, [password, config]);
46
-
47
- const passwordsMatch = useMemo(() => {
48
- if (!password || !confirmPassword) {
49
- return false;
50
- }
51
- return password === confirmPassword;
52
- }, [password, confirmPassword]);
53
-
54
- const confirmationError = useMemo(() => {
55
- if (!confirmPassword) {
56
- return null;
57
- }
58
- const result = validatePasswordConfirmation(password, confirmPassword);
59
- return result.error ?? null;
60
- }, [password, confirmPassword]);
61
-
62
- const isValid = useMemo(() => {
63
- return passwordRequirements.hasMinLength && passwordsMatch;
64
- }, [passwordRequirements, passwordsMatch]);
65
-
66
- return {
67
- passwordRequirements,
68
- passwordsMatch,
69
- isValid,
70
- confirmationError,
71
- };
35
+ // PERFORMANCE: Use utility function for batch calculation
36
+ const result = useMemo(() => {
37
+ const validation = calculatePasswordValidation({
38
+ password,
39
+ confirmPassword,
40
+ config: options?.passwordConfig,
41
+ });
42
+
43
+ // Map to expected return type
44
+ return {
45
+ passwordRequirements: validation.requirements,
46
+ passwordsMatch: validation.passwordsMatch,
47
+ isValid: validation.isValid,
48
+ confirmationError: validation.confirmationError,
49
+ };
50
+ }, [password, confirmPassword, options?.passwordConfig]);
51
+
52
+ return result;
72
53
  }
73
54