@umituz/react-native-auth 3.6.73 → 3.6.75

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": "3.6.73",
3
+ "version": "3.6.75",
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",
@@ -7,9 +7,6 @@ export interface PasswordConfig {
7
7
  minLength: number;
8
8
  }
9
9
 
10
- /**
11
- * Social authentication provider configuration
12
- */
13
10
  export interface SocialProviderConfig {
14
11
  enabled: boolean;
15
12
  }
@@ -20,21 +17,13 @@ export interface GoogleAuthConfig extends SocialProviderConfig {
20
17
  androidClientId?: string;
21
18
  }
22
19
 
23
- export interface AppleAuthConfig extends SocialProviderConfig {
24
- // Apple Sign In doesn't require additional config for basic usage
25
- }
20
+ export interface AppleAuthConfig extends SocialProviderConfig {}
26
21
 
27
- /**
28
- * Social authentication configuration
29
- */
30
22
  export interface SocialAuthConfig {
31
23
  google?: GoogleAuthConfig;
32
24
  apple?: AppleAuthConfig;
33
25
  }
34
26
 
35
- /**
36
- * Supported social auth providers
37
- */
38
27
  export type SocialAuthProvider = "google" | "apple";
39
28
 
40
29
  export interface AuthConfig {
@@ -42,23 +31,16 @@ export interface AuthConfig {
42
31
  social?: SocialAuthConfig;
43
32
  }
44
33
 
45
- export const DEFAULT_PASSWORD_CONFIG: PasswordConfig = {
46
- minLength: 6,
47
- };
48
-
34
+ export const DEFAULT_PASSWORD_CONFIG: PasswordConfig = { minLength: 6 };
49
35
  export const DEFAULT_SOCIAL_CONFIG: SocialAuthConfig = {
50
36
  google: { enabled: false },
51
37
  apple: { enabled: false },
52
38
  };
53
-
54
39
  export const DEFAULT_AUTH_CONFIG: AuthConfig = {
55
40
  password: DEFAULT_PASSWORD_CONFIG,
56
41
  social: DEFAULT_SOCIAL_CONFIG,
57
42
  };
58
43
 
59
- /**
60
- * Configuration validation error
61
- */
62
44
  export class AuthConfigValidationError extends Error {
63
45
  constructor(message: string, public readonly field: string) {
64
46
  super(message);
@@ -66,58 +48,29 @@ export class AuthConfigValidationError extends Error {
66
48
  }
67
49
  }
68
50
 
69
- /**
70
- * Validate authentication configuration
71
- * @throws {AuthConfigValidationError} if configuration is invalid
72
- */
73
51
  export function validateAuthConfig(config: Partial<AuthConfig>): void {
74
- // Validate password config
75
52
  if (config.password) {
76
53
  if (typeof config.password.minLength !== "number") {
77
- throw new AuthConfigValidationError(
78
- "Password minLength must be a number",
79
- "password.minLength"
80
- );
54
+ throw new AuthConfigValidationError("Password minLength must be a number", "password.minLength");
81
55
  }
82
56
  if (config.password.minLength < 4) {
83
- throw new AuthConfigValidationError(
84
- "Password minLength must be at least 4 characters",
85
- "password.minLength"
86
- );
57
+ throw new AuthConfigValidationError("Password minLength must be at least 4 characters", "password.minLength");
87
58
  }
88
59
  if (config.password.minLength > 128) {
89
- throw new AuthConfigValidationError(
90
- "Password minLength must not exceed 128 characters",
91
- "password.minLength"
92
- );
60
+ throw new AuthConfigValidationError("Password minLength must not exceed 128 characters", "password.minLength");
93
61
  }
94
62
  }
95
63
 
96
- // Validate social auth config
97
- if (config.social) {
98
- if (config.social.google) {
99
- const googleConfig = config.social.google;
100
- if (googleConfig.enabled) {
101
- // At least one client ID should be provided if enabled
102
- if (!googleConfig.webClientId && !googleConfig.iosClientId && !googleConfig.androidClientId) {
103
- throw new AuthConfigValidationError(
104
- "At least one Google client ID (web, iOS, or Android) must be provided when Google Sign-In is enabled",
105
- "social.google"
106
- );
107
- }
108
- }
64
+ if (config.social?.google?.enabled) {
65
+ const googleConfig = config.social.google;
66
+ if (!googleConfig.webClientId && !googleConfig.iosClientId && !googleConfig.androidClientId) {
67
+ throw new AuthConfigValidationError("At least one Google client ID must be provided when Google Sign-In is enabled", "social.google");
109
68
  }
110
69
  }
111
70
  }
112
71
 
113
- /**
114
- * Sanitize and merge auth config with defaults
115
- * Ensures valid configuration values
116
- */
117
72
  export function sanitizeAuthConfig(config: Partial<AuthConfig> = {}): AuthConfig {
118
- // Validate first
119
73
  validateAuthConfig(config);
120
-
121
74
  return {
122
75
  password: {
123
76
  minLength: config.password?.minLength ?? DEFAULT_PASSWORD_CONFIG.minLength,
package/src/index.ts CHANGED
@@ -10,97 +10,30 @@
10
10
  // =============================================================================
11
11
 
12
12
  export type { AuthUser, AuthProviderType } from './domain/entities/AuthUser';
13
- export {
14
- AuthError,
15
- AuthInitializationError,
16
- AuthConfigurationError,
17
- AuthValidationError,
18
- AuthNetworkError,
19
- AuthUserNotFoundError,
20
- AuthWrongPasswordError,
21
- AuthEmailAlreadyInUseError,
22
- AuthWeakPasswordError,
23
- AuthInvalidEmailError,
24
- } from './domain/errors/AuthError';
25
-
26
- export type {
27
- AuthConfig,
28
- PasswordConfig,
29
- SocialAuthConfig,
30
- SocialProviderConfig,
31
- GoogleAuthConfig,
32
- AppleAuthConfig,
33
- SocialAuthProvider,
34
- } from './domain/value-objects/AuthConfig';
35
- export {
36
- DEFAULT_AUTH_CONFIG,
37
- DEFAULT_PASSWORD_CONFIG,
38
- DEFAULT_SOCIAL_CONFIG,
39
- } from './domain/value-objects/AuthConfig';
40
-
41
13
  export type { UserProfile, UpdateProfileParams } from './domain/entities/UserProfile';
14
+ export { AuthError, AuthInitializationError, AuthConfigurationError, AuthValidationError, AuthNetworkError, AuthUserNotFoundError, AuthWrongPasswordError, AuthEmailAlreadyInUseError, AuthWeakPasswordError, AuthInvalidEmailError } from './domain/errors/AuthError';
15
+ export type { AuthConfig, PasswordConfig, SocialAuthConfig, SocialProviderConfig, GoogleAuthConfig, AppleAuthConfig, SocialAuthProvider } from './domain/value-objects/AuthConfig';
16
+ export { DEFAULT_AUTH_CONFIG, DEFAULT_PASSWORD_CONFIG, DEFAULT_SOCIAL_CONFIG } from './domain/value-objects/AuthConfig';
42
17
 
43
18
  // =============================================================================
44
19
  // APPLICATION LAYER
45
20
  // =============================================================================
46
21
 
47
- export type {
48
- IAuthProvider,
49
- AuthCredentials,
50
- SignUpCredentials,
51
- SocialSignInResult,
52
- } from './application/ports/IAuthProvider';
22
+ export type { IAuthProvider, AuthCredentials, SignUpCredentials, SocialSignInResult } from './application/ports/IAuthProvider';
53
23
 
54
24
  // =============================================================================
55
25
  // INFRASTRUCTURE LAYER
56
26
  // =============================================================================
57
27
 
58
28
  export { FirebaseAuthProvider } from './infrastructure/providers/FirebaseAuthProvider';
59
- export {
60
- AuthService,
61
- initializeAuthService,
62
- getAuthService,
63
- resetAuthService,
64
- } from './infrastructure/services/AuthService';
29
+ export { AuthService, initializeAuthService, getAuthService, resetAuthService } from './infrastructure/services/AuthService';
65
30
  export type { IStorageProvider } from './infrastructure/types/Storage.types';
66
- export {
67
- createStorageProvider,
68
- StorageProviderAdapter,
69
- } from './infrastructure/adapters/StorageProviderAdapter';
70
- export {
71
- initializeAuth,
72
- isAuthInitialized,
73
- resetAuthInitialization,
74
- } from './infrastructure/services/initializeAuth';
31
+ export { createStorageProvider, StorageProviderAdapter } from './infrastructure/adapters/StorageProviderAdapter';
32
+ export { initializeAuth, isAuthInitialized, resetAuthInitialization } from './infrastructure/services/initializeAuth';
75
33
  export type { InitializeAuthOptions } from './infrastructure/services/initializeAuth';
76
-
77
- // =============================================================================
78
- // VALIDATION
79
- // =============================================================================
80
-
81
- export {
82
- validateEmail,
83
- validatePasswordForLogin,
84
- validatePasswordForRegister,
85
- validatePasswordConfirmation,
86
- validateDisplayName,
87
- } from './infrastructure/utils/AuthValidation';
88
- export type {
89
- ValidationResult,
90
- PasswordStrengthResult,
91
- PasswordRequirements,
92
- } from './infrastructure/utils/AuthValidation';
93
-
94
- export {
95
- SECURITY_LIMITS,
96
- sanitizeWhitespace,
97
- sanitizeEmail,
98
- sanitizePassword,
99
- sanitizeName,
100
- sanitizeText,
101
- containsDangerousChars,
102
- isWithinLengthLimit,
103
- } from './infrastructure/utils/validation/sanitization';
34
+ export { validateEmail, validatePasswordForLogin, validatePasswordForRegister, validatePasswordConfirmation, validateDisplayName } from './infrastructure/utils/AuthValidation';
35
+ export type { ValidationResult, PasswordStrengthResult, PasswordRequirements } from './infrastructure/utils/AuthValidation';
36
+ export { SECURITY_LIMITS, sanitizeWhitespace, sanitizeEmail, sanitizePassword, sanitizeName, sanitizeText, containsDangerousChars, isWithinLengthLimit } from './infrastructure/utils/validation/sanitization';
104
37
  export type { SecurityLimitKey } from './infrastructure/utils/validation/sanitization';
105
38
 
106
39
  // =============================================================================
@@ -109,37 +42,26 @@ export type { SecurityLimitKey } from './infrastructure/utils/validation/sanitiz
109
42
 
110
43
  export { useAuth } from './presentation/hooks/useAuth';
111
44
  export type { UseAuthResult } from './presentation/hooks/useAuth';
112
-
113
45
  export { useLoginForm } from './presentation/hooks/useLoginForm';
114
46
  export type { UseLoginFormConfig, UseLoginFormResult } from './presentation/hooks/useLoginForm';
115
-
116
47
  export { useRegisterForm } from './presentation/hooks/useRegisterForm';
117
48
  export type { UseRegisterFormConfig, UseRegisterFormResult } from './presentation/hooks/useRegisterForm';
118
-
119
49
  export { useAuthRequired } from './presentation/hooks/useAuthRequired';
120
50
  export { useRequireAuth, useUserId } from './presentation/hooks/useRequireAuth';
121
-
122
51
  export { useUserProfile } from './presentation/hooks/useUserProfile';
123
52
  export type { UserProfileData, UseUserProfileParams } from './presentation/hooks/useUserProfile';
124
-
125
53
  export { useAccountManagement } from './presentation/hooks/useAccountManagement';
126
54
  export type { UseAccountManagementReturn } from './presentation/hooks/useAccountManagement';
127
-
128
55
  export { useProfileUpdate } from './presentation/hooks/useProfileUpdate';
129
56
  export type { UseProfileUpdateReturn } from './presentation/hooks/useProfileUpdate';
130
-
131
57
  export { useProfileEdit } from './presentation/hooks/useProfileEdit';
132
58
  export type { ProfileEditFormState, UseProfileEditReturn } from './presentation/hooks/useProfileEdit';
133
-
134
59
  export { useSocialLogin } from './presentation/hooks/useSocialLogin';
135
60
  export type { UseSocialLoginConfig, UseSocialLoginResult } from './presentation/hooks/useSocialLogin';
136
-
137
61
  export { useGoogleAuth } from './presentation/hooks/useGoogleAuth';
138
62
  export type { UseGoogleAuthResult } from './presentation/hooks/useGoogleAuth';
139
-
140
63
  export { useAppleAuth } from './presentation/hooks/useAppleAuth';
141
64
  export type { UseAppleAuthResult } from './presentation/hooks/useAppleAuth';
142
-
143
65
  export { useAuthBottomSheet } from './presentation/hooks/useAuthBottomSheet';
144
66
  export type { SocialAuthConfiguration } from './presentation/hooks/useAuthBottomSheet';
145
67
 
@@ -149,22 +71,16 @@ export type { SocialAuthConfiguration } from './presentation/hooks/useAuthBottom
149
71
 
150
72
  export { AuthProvider } from './presentation/providers/AuthProvider';
151
73
  export type { ErrorFallbackProps } from './presentation/providers/AuthProvider';
152
-
153
74
  export { LoginScreen } from './presentation/screens/LoginScreen';
154
75
  export type { LoginScreenProps } from './presentation/screens/LoginScreen';
155
-
156
76
  export { RegisterScreen } from './presentation/screens/RegisterScreen';
157
77
  export type { RegisterScreenProps } from './presentation/screens/RegisterScreen';
158
-
159
78
  export { AccountScreen } from './presentation/screens/AccountScreen';
160
79
  export type { AccountScreenProps, AccountScreenConfig } from './presentation/screens/AccountScreen';
161
-
162
80
  export { EditProfileScreen } from './presentation/screens/EditProfileScreen';
163
81
  export type { EditProfileScreenProps } from './presentation/screens/EditProfileScreen';
164
-
165
82
  export { AuthNavigator } from './presentation/navigation/AuthNavigator';
166
83
  export type { AuthStackParamList } from './presentation/navigation/AuthNavigator';
167
-
168
84
  export { AuthBottomSheet } from './presentation/components/AuthBottomSheet';
169
85
  export { ProfileSection } from './presentation/components/ProfileSection';
170
86
  export type { ProfileSectionProps, ProfileSectionConfig } from './presentation/components/ProfileSection';
@@ -175,28 +91,15 @@ export type { ProfileSectionProps, ProfileSectionConfig } from './presentation/c
175
91
 
176
92
  export { useAuthStore } from './presentation/stores/authStore';
177
93
  export { useAuthModalStore } from './presentation/stores/authModalStore';
178
- export {
179
- initializeAuthListener,
180
- resetAuthListener,
181
- isAuthListenerInitialized,
182
- } from './presentation/stores/initializeAuthListener';
183
- export type { AuthState, AuthActions, UserType } from './types/auth-store.types';
184
- export type { AuthListenerOptions } from './types/auth-store.types';
94
+ export { initializeAuthListener, resetAuthListener, isAuthListenerInitialized } from './presentation/stores/initializeAuthListener';
95
+ export type { AuthState, AuthActions, UserType, AuthListenerOptions } from './types/auth-store.types';
185
96
  export * from './presentation/stores/auth.selectors';
186
97
 
187
98
  // =============================================================================
188
- // UTILITIES
189
- // =============================================================================
190
-
191
- export {
192
- getAuthErrorLocalizationKey,
193
- resolveErrorMessage,
194
- } from './presentation/utils/getAuthErrorMessage';
195
-
196
- // =============================================================================
197
- // INIT MODULE
99
+ // UTILITIES & INIT
198
100
  // =============================================================================
199
101
 
102
+ export { getAuthErrorLocalizationKey, resolveErrorMessage } from './presentation/utils/getAuthErrorMessage';
200
103
  export { createAuthInitModule } from './init';
201
104
  export type { AuthInitModuleConfig } from './init/createAuthInitModule';
202
105
 
@@ -13,12 +13,7 @@ import {
13
13
  linkWithCredential,
14
14
  type Auth,
15
15
  } from "firebase/auth";
16
-
17
- import type {
18
- IAuthProvider,
19
- AuthCredentials,
20
- SignUpCredentials,
21
- } from "../../application/ports/IAuthProvider";
16
+ import type { IAuthProvider, AuthCredentials, SignUpCredentials } from "../../application/ports/IAuthProvider";
22
17
  import type { AuthUser } from "../../domain/entities/AuthUser";
23
18
  import { mapFirebaseAuthError } from "../utils/AuthErrorMapper";
24
19
  import { mapToAuthUser } from "../utils/UserMapper";
@@ -27,15 +22,11 @@ export class FirebaseAuthProvider implements IAuthProvider {
27
22
  private auth: Auth | null = null;
28
23
 
29
24
  constructor(auth?: Auth) {
30
- if (auth) {
31
- this.auth = auth;
32
- }
25
+ if (auth) this.auth = auth;
33
26
  }
34
27
 
35
28
  initialize(): Promise<void> {
36
- if (!this.auth) {
37
- throw new Error("Firebase Auth instance must be provided");
38
- }
29
+ if (!this.auth) throw new Error("Firebase Auth instance must be provided");
39
30
  return Promise.resolve();
40
31
  }
41
32
 
@@ -48,20 +39,12 @@ export class FirebaseAuthProvider implements IAuthProvider {
48
39
  }
49
40
 
50
41
  async signIn(credentials: AuthCredentials): Promise<AuthUser> {
51
- if (!this.auth) {
52
- throw new Error("Firebase Auth is not initialized");
53
- }
42
+ if (!this.auth) throw new Error("Firebase Auth is not initialized");
54
43
 
55
44
  try {
56
- const userCredential = await signInWithEmailAndPassword(
57
- this.auth,
58
- credentials.email.trim(),
59
- credentials.password
60
- );
45
+ const userCredential = await signInWithEmailAndPassword(this.auth, credentials.email.trim(), credentials.password);
61
46
  const user = mapToAuthUser(userCredential.user);
62
- if (!user) {
63
- throw new Error("Failed to sign in");
64
- }
47
+ if (!user) throw new Error("Failed to sign in");
65
48
  return user;
66
49
  } catch (error: unknown) {
67
50
  throw mapFirebaseAuthError(error);
@@ -69,55 +52,31 @@ export class FirebaseAuthProvider implements IAuthProvider {
69
52
  }
70
53
 
71
54
  async signUp(credentials: SignUpCredentials): Promise<AuthUser> {
72
- if (!this.auth) {
73
- throw new Error("Firebase Auth is not initialized");
74
- }
55
+ if (!this.auth) throw new Error("Firebase Auth is not initialized");
75
56
 
76
57
  try {
77
58
  const currentUser = this.auth.currentUser;
78
59
  const isAnonymous = currentUser?.isAnonymous ?? false;
79
-
80
60
  let userCredential;
81
61
 
82
- // Convert anonymous user to permanent account
83
62
  if (currentUser && isAnonymous) {
84
- // Reload user to refresh token before linking (prevents token-expired errors)
85
- try {
86
- await currentUser.reload();
87
- } catch {
88
- // Reload failed, proceed with link anyway
89
- }
90
-
91
- const credential = EmailAuthProvider.credential(
92
- credentials.email.trim(),
93
- credentials.password
94
- );
95
-
63
+ try { await currentUser.reload(); } catch { /* Reload failed, proceed */ }
64
+ const credential = EmailAuthProvider.credential(credentials.email.trim(), credentials.password);
96
65
  userCredential = await linkWithCredential(currentUser, credential);
97
66
  } else {
98
- // Create new user
99
- userCredential = await createUserWithEmailAndPassword(
100
- this.auth,
101
- credentials.email.trim(),
102
- credentials.password
103
- );
67
+ userCredential = await createUserWithEmailAndPassword(this.auth, credentials.email.trim(), credentials.password);
104
68
  }
105
69
 
106
70
  if (credentials.displayName && userCredential.user) {
107
71
  try {
108
- await updateProfile(userCredential.user, {
109
- displayName: credentials.displayName.trim(),
110
- });
72
+ await updateProfile(userCredential.user, { displayName: credentials.displayName.trim() });
111
73
  } catch {
112
- // Silently fail - the account was created successfully,
113
- // only the display name update failed. User can update it later.
74
+ /* Display name update failed, account created successfully */
114
75
  }
115
76
  }
116
77
 
117
78
  const user = mapToAuthUser(userCredential.user);
118
- if (!user) {
119
- throw new Error("Failed to create user account");
120
- }
79
+ if (!user) throw new Error("Failed to create user account");
121
80
  return user;
122
81
  } catch (error: unknown) {
123
82
  throw mapFirebaseAuthError(error);
@@ -125,10 +84,7 @@ export class FirebaseAuthProvider implements IAuthProvider {
125
84
  }
126
85
 
127
86
  async signOut(): Promise<void> {
128
- if (!this.auth) {
129
- return;
130
- }
131
-
87
+ if (!this.auth) return;
132
88
  try {
133
89
  await firebaseSignOut(this.auth);
134
90
  } catch (error: unknown) {
@@ -137,13 +93,9 @@ export class FirebaseAuthProvider implements IAuthProvider {
137
93
  }
138
94
 
139
95
  getCurrentUser(): AuthUser | null {
140
- if (!this.auth) {
141
- return null;
142
- }
96
+ if (!this.auth) return null;
143
97
  const currentUser = this.auth.currentUser;
144
- if (!currentUser) {
145
- return null;
146
- }
98
+ if (!currentUser) return null;
147
99
  return mapToAuthUser(currentUser);
148
100
  }
149
101
 
@@ -152,9 +104,6 @@ export class FirebaseAuthProvider implements IAuthProvider {
152
104
  callback(null);
153
105
  return () => {};
154
106
  }
155
-
156
- return onAuthStateChanged(this.auth, (user) => {
157
- callback(mapToAuthUser(user));
158
- });
107
+ return onAuthStateChanged(this.auth, (user) => callback(mapToAuthUser(user)));
159
108
  }
160
109
  }
@@ -23,9 +23,7 @@ export class AuthService {
23
23
  private config: AuthConfig;
24
24
 
25
25
  constructor(config: Partial<AuthConfig> = {}, storageProvider?: IStorageProvider) {
26
- // Validate and sanitize configuration
27
26
  this.config = sanitizeAuthConfig(config);
28
-
29
27
  this.anonymousModeService = new AnonymousModeService();
30
28
  this.storageProvider = storageProvider;
31
29
  }
@@ -87,17 +85,12 @@ export class AuthService {
87
85
  }
88
86
 
89
87
  async setAnonymousMode(): Promise<void> {
90
- if (!this.storageProvider) {
91
- throw new Error("Storage provider is required for anonymous mode");
92
- }
88
+ if (!this.storageProvider) throw new Error("Storage provider is required for anonymous mode");
93
89
  await this.anonymousModeService.enable(this.storageProvider);
94
90
  }
95
91
 
96
92
  getCurrentUser(): AuthUser | null {
97
93
  if (!this.initialized) return null;
98
- // Return the actual Firebase user regardless of anonymous mode
99
- // The caller should check the user's isAnonymous property if needed
100
- // This ensures proper anonymous to registered user conversion
101
94
  return this.repositoryInstance.getCurrentUser();
102
95
  }
103
96
 
@@ -47,12 +47,7 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
47
47
  const handleLogout = () => {
48
48
  alert.show(AlertType.WARNING, AlertMode.MODAL, logoutConfirmTitle, logoutConfirmMessage, {
49
49
  actions: [
50
- {
51
- id: "cancel",
52
- label: cancelText,
53
- style: "secondary",
54
- onPress: () => {},
55
- },
50
+ { id: "cancel", label: cancelText, style: "secondary", onPress: () => {} },
56
51
  {
57
52
  id: "confirm",
58
53
  label: logoutText,
@@ -72,12 +67,7 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
72
67
  const handleDeleteAccount = () => {
73
68
  alert.show(AlertType.ERROR, AlertMode.MODAL, deleteConfirmTitle, deleteConfirmMessage, {
74
69
  actions: [
75
- {
76
- id: "cancel",
77
- label: cancelText,
78
- style: "secondary",
79
- onPress: () => {},
80
- },
70
+ { id: "cancel", label: cancelText, style: "secondary", onPress: () => {} },
81
71
  {
82
72
  id: "confirm",
83
73
  label: deleteAccountText,
@@ -97,40 +87,22 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
97
87
  return (
98
88
  <View style={styles.container}>
99
89
  {showChangePassword && onChangePassword && changePasswordText && (
100
- <TouchableOpacity
101
- style={[actionButtonStyle.container, { borderColor: tokens.colors.border }]}
102
- onPress={onChangePassword}
103
- activeOpacity={0.7}
104
- >
90
+ <TouchableOpacity style={[actionButtonStyle.container, { borderColor: tokens.colors.border }]} onPress={onChangePassword} activeOpacity={0.7}>
105
91
  <AtomicIcon name="key-outline" size="md" color="textPrimary" />
106
- <AtomicText style={actionButtonStyle.text} color="textPrimary">
107
- {changePasswordText}
108
- </AtomicText>
92
+ <AtomicText style={actionButtonStyle.text} color="textPrimary">{changePasswordText}</AtomicText>
109
93
  <AtomicIcon name="chevron-forward" size="sm" color="textSecondary" />
110
94
  </TouchableOpacity>
111
95
  )}
112
96
 
113
- <TouchableOpacity
114
- style={[actionButtonStyle.container, { borderColor: tokens.colors.border }]}
115
- onPress={handleLogout}
116
- activeOpacity={0.7}
117
- >
97
+ <TouchableOpacity style={[actionButtonStyle.container, { borderColor: tokens.colors.border }]} onPress={handleLogout} activeOpacity={0.7}>
118
98
  <AtomicIcon name="log-out-outline" size="md" color="error" />
119
- <AtomicText style={actionButtonStyle.text} color="error">
120
- {logoutText}
121
- </AtomicText>
99
+ <AtomicText style={actionButtonStyle.text} color="error">{logoutText}</AtomicText>
122
100
  <AtomicIcon name="chevron-forward" size="sm" color="textSecondary" />
123
101
  </TouchableOpacity>
124
102
 
125
- <TouchableOpacity
126
- style={[actionButtonStyle.container, { borderColor: tokens.colors.border }]}
127
- onPress={handleDeleteAccount}
128
- activeOpacity={0.7}
129
- >
103
+ <TouchableOpacity style={[actionButtonStyle.container, { borderColor: tokens.colors.border }]} onPress={handleDeleteAccount} activeOpacity={0.7}>
130
104
  <AtomicIcon name="trash-outline" size="md" color="error" />
131
- <AtomicText style={actionButtonStyle.text} color="error">
132
- {deleteAccountText}
133
- </AtomicText>
105
+ <AtomicText style={actionButtonStyle.text} color="error">{deleteAccountText}</AtomicText>
134
106
  <AtomicIcon name="chevron-forward" size="sm" color="textSecondary" />
135
107
  </TouchableOpacity>
136
108
  </View>
@@ -138,7 +110,5 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
138
110
  };
139
111
 
140
112
  const styles = StyleSheet.create({
141
- container: {
142
- gap: 12,
143
- },
113
+ container: { gap: 12 },
144
114
  });
@@ -9,128 +9,98 @@ import { useAppDesignTokens, AtomicText, AtomicIcon, AtomicAvatar } from "@umitu
9
9
  import { ProfileBenefitsList } from "./ProfileBenefitsList";
10
10
 
11
11
  export interface ProfileSectionConfig {
12
- displayName?: string;
13
- userId?: string;
14
- isAnonymous: boolean;
15
- avatarUrl?: string;
16
- accountSettingsRoute?: string;
17
- benefits?: string[];
12
+ displayName?: string;
13
+ userId?: string;
14
+ isAnonymous: boolean;
15
+ avatarUrl?: string;
16
+ accountSettingsRoute?: string;
17
+ benefits?: string[];
18
18
  }
19
19
 
20
20
  export interface ProfileSectionProps {
21
- profile: ProfileSectionConfig;
22
- onPress?: () => void;
23
- onSignIn?: () => void;
24
- signInText?: string;
25
- anonymousText?: string;
21
+ profile: ProfileSectionConfig;
22
+ onPress?: () => void;
23
+ onSignIn?: () => void;
24
+ signInText?: string;
25
+ anonymousText?: string;
26
26
  }
27
27
 
28
28
  export const ProfileSection: React.FC<ProfileSectionProps> = ({
29
- profile,
30
- onPress,
31
- onSignIn,
32
- signInText,
33
- anonymousText,
29
+ profile,
30
+ onPress,
31
+ onSignIn,
32
+ signInText,
33
+ anonymousText,
34
34
  }) => {
35
- const tokens = useAppDesignTokens();
35
+ const tokens = useAppDesignTokens();
36
36
 
37
- const handlePress = () => {
38
- if (profile.isAnonymous && onSignIn) {
39
- onSignIn();
40
- } else if (onPress) {
41
- onPress();
42
- }
43
- };
37
+ const handlePress = () => {
38
+ if (profile.isAnonymous && onSignIn) {
39
+ onSignIn();
40
+ } else if (onPress) {
41
+ onPress();
42
+ }
43
+ };
44
44
 
45
- return (
46
- <TouchableOpacity
47
- style={[styles.container, { backgroundColor: tokens.colors.surface }]}
48
- onPress={handlePress}
49
- activeOpacity={0.7}
50
- disabled={!onPress && !onSignIn}
51
- >
52
- <View style={styles.content}>
53
- <View style={styles.avatarContainer}>
54
- <AtomicAvatar
55
- source={profile.avatarUrl ? { uri: profile.avatarUrl } : undefined}
56
- name={profile.displayName || (profile.isAnonymous ? anonymousText : signInText)}
57
- size="md"
58
- />
59
- </View>
45
+ return (
46
+ <TouchableOpacity
47
+ style={[styles.container, { backgroundColor: tokens.colors.surface }]}
48
+ onPress={handlePress}
49
+ activeOpacity={0.7}
50
+ disabled={!onPress && !onSignIn}
51
+ >
52
+ <View style={styles.content}>
53
+ <View style={styles.avatarContainer}>
54
+ <AtomicAvatar
55
+ source={profile.avatarUrl ? { uri: profile.avatarUrl } : undefined}
56
+ name={profile.displayName || (profile.isAnonymous ? anonymousText : signInText)}
57
+ size="md"
58
+ />
59
+ </View>
60
60
 
61
- <View style={styles.info}>
62
- <AtomicText
63
- type="titleMedium"
64
- color="textPrimary"
65
- numberOfLines={1}
66
- style={styles.displayName}
67
- >
68
- {profile.displayName}
69
- </AtomicText>
70
- {profile.userId && (
71
- <AtomicText
72
- type="bodySmall"
73
- color="textSecondary"
74
- numberOfLines={1}
75
- >
76
- {profile.userId}
77
- </AtomicText>
78
- )}
79
- </View>
61
+ <View style={styles.info}>
62
+ <AtomicText type="titleMedium" color="textPrimary" numberOfLines={1} style={styles.displayName}>
63
+ {profile.displayName}
64
+ </AtomicText>
65
+ {profile.userId && (
66
+ <AtomicText type="bodySmall" color="textSecondary" numberOfLines={1}>
67
+ {profile.userId}
68
+ </AtomicText>
69
+ )}
70
+ </View>
80
71
 
81
- {onPress && !profile.isAnonymous && (
82
- <AtomicIcon name="chevron-forward" size="sm" color="textSecondary" />
83
- )}
84
- </View>
72
+ {onPress && !profile.isAnonymous && (
73
+ <AtomicIcon name="chevron-forward" size="sm" color="textSecondary" />
74
+ )}
75
+ </View>
85
76
 
86
- {profile.isAnonymous && onSignIn && (
87
- <View style={[styles.ctaContainer, { borderTopColor: tokens.colors.border }]}>
88
- {profile.benefits && profile.benefits.length > 0 && (
89
- <ProfileBenefitsList benefits={profile.benefits} />
90
- )}
77
+ {profile.isAnonymous && onSignIn && (
78
+ <View style={[styles.ctaContainer, { borderTopColor: tokens.colors.border }]}>
79
+ {profile.benefits && profile.benefits.length > 0 && (
80
+ <ProfileBenefitsList benefits={profile.benefits} />
81
+ )}
91
82
 
92
- <TouchableOpacity
93
- style={[styles.ctaButton, { backgroundColor: tokens.colors.primary }]}
94
- onPress={onSignIn}
95
- activeOpacity={0.8}
96
- >
97
- <AtomicText type="labelLarge" style={{ color: tokens.colors.onPrimary }}>
98
- {signInText}
99
- </AtomicText>
100
- </TouchableOpacity>
101
- </View>
102
- )}
103
- </TouchableOpacity>
104
- );
83
+ <TouchableOpacity
84
+ style={[styles.ctaButton, { backgroundColor: tokens.colors.primary }]}
85
+ onPress={onSignIn}
86
+ activeOpacity={0.8}
87
+ >
88
+ <AtomicText type="labelLarge" style={{ color: tokens.colors.onPrimary }}>
89
+ {signInText}
90
+ </AtomicText>
91
+ </TouchableOpacity>
92
+ </View>
93
+ )}
94
+ </TouchableOpacity>
95
+ );
105
96
  };
106
97
 
107
98
  const styles = StyleSheet.create({
108
- container: {
109
- borderRadius: 12,
110
- padding: 16,
111
- marginBottom: 16,
112
- },
113
- content: {
114
- flexDirection: "row",
115
- alignItems: "center",
116
- },
117
- avatarContainer: {
118
- marginRight: 12,
119
- },
120
- info: {
121
- flex: 1,
122
- },
123
- displayName: {
124
- marginBottom: 2,
125
- },
126
- ctaContainer: {
127
- marginTop: 12,
128
- paddingTop: 12,
129
- borderTopWidth: 1,
130
- },
131
- ctaButton: {
132
- paddingVertical: 12,
133
- borderRadius: 8,
134
- alignItems: "center",
135
- },
99
+ container: { borderRadius: 12, padding: 16, marginBottom: 16 },
100
+ content: { flexDirection: "row", alignItems: "center" },
101
+ avatarContainer: { marginRight: 12 },
102
+ info: { flex: 1 },
103
+ displayName: { marginBottom: 2 },
104
+ ctaContainer: { marginTop: 12, paddingTop: 12, borderTopWidth: 1 },
105
+ ctaButton: { paddingVertical: 12, borderRadius: 8, alignItems: "center" },
136
106
  });
@@ -119,10 +119,7 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
119
119
  style={styles.input}
120
120
  />
121
121
  {password.length > 0 && (
122
- <PasswordStrengthIndicator
123
- translations={translations.passwordStrength}
124
- requirements={passwordRequirements}
125
- />
122
+ <PasswordStrengthIndicator translations={translations.passwordStrength} requirements={passwordRequirements} />
126
123
  )}
127
124
 
128
125
  <AtomicInput
@@ -144,10 +141,7 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
144
141
  style={styles.input}
145
142
  />
146
143
  {confirmPassword.length > 0 && (
147
- <PasswordMatchIndicator
148
- translations={translations.passwordMatch}
149
- isMatch={passwordsMatch}
150
- />
144
+ <PasswordMatchIndicator translations={translations.passwordMatch} isMatch={passwordsMatch} />
151
145
  )}
152
146
 
153
147
  <AuthErrorDisplay error={displayError} />
@@ -155,24 +149,14 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
155
149
  <AtomicButton
156
150
  variant="primary"
157
151
  onPress={() => { void handleSignUp(); }}
158
- disabled={
159
- loading ||
160
- !email.trim() ||
161
- !password.trim() ||
162
- !confirmPassword.trim()
163
- }
152
+ disabled={loading || !email.trim() || !password.trim() || !confirmPassword.trim()}
164
153
  fullWidth
165
154
  style={styles.signUpButton}
166
155
  >
167
156
  {translations.signUp}
168
157
  </AtomicButton>
169
158
 
170
- <AuthLink
171
- text={translations.alreadyHaveAccount}
172
- linkText={translations.signIn}
173
- onPress={onNavigateToLogin}
174
- disabled={loading}
175
- />
159
+ <AuthLink text={translations.alreadyHaveAccount} linkText={translations.signIn} onPress={onNavigateToLogin} disabled={loading} />
176
160
 
177
161
  <AuthLegalLinks
178
162
  translations={translations.legal}
@@ -187,12 +171,6 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
187
171
  };
188
172
 
189
173
  const styles = StyleSheet.create({
190
- input: {
191
- marginBottom: 20,
192
- },
193
- signUpButton: {
194
- minHeight: 52,
195
- marginBottom: 16,
196
- marginTop: 8,
197
- },
174
+ input: { marginBottom: 20 },
175
+ signUpButton: { minHeight: 52, marginBottom: 16, marginTop: 8 },
198
176
  });
@@ -1,9 +1,6 @@
1
1
  /**
2
2
  * useAuth Hook
3
3
  * React hook for authentication state management
4
- *
5
- * Uses centralized Zustand store for auth state.
6
- * Single source of truth - no duplicate subscriptions.
7
4
  */
8
5
 
9
6
  import { useCallback } from "react";
@@ -32,44 +29,23 @@ import {
32
29
  import type { AuthUser } from "../../domain/entities/AuthUser";
33
30
 
34
31
  export interface UseAuthResult {
35
- /** Current authenticated user */
36
32
  user: AuthUser | null;
37
- /** Current user ID (uid) */
38
33
  userId: string | null;
39
- /** Current user type */
40
34
  userType: UserType;
41
- /** Whether auth state is loading */
42
35
  loading: boolean;
43
- /** Whether auth is ready (initialized and not loading) */
44
36
  isAuthReady: boolean;
45
- /** Whether user is anonymous */
46
37
  isAnonymous: boolean;
47
- /** Whether user is authenticated (not anonymous) */
48
38
  isAuthenticated: boolean;
49
- /** Whether user is a registered user (authenticated AND not anonymous) */
50
39
  isRegisteredUser: boolean;
51
- /** Current error message */
52
40
  error: string | null;
53
- /** Sign up function */
54
41
  signUp: (email: string, password: string, displayName?: string) => Promise<void>;
55
- /** Sign in function */
56
42
  signIn: (email: string, password: string) => Promise<void>;
57
- /** Sign out function */
58
43
  signOut: () => Promise<void>;
59
- /** Continue anonymously function */
60
44
  continueAnonymously: () => Promise<void>;
61
- /** Set error manually (for form validation, etc.) */
62
45
  setError: (error: string | null) => void;
63
46
  }
64
47
 
65
- /**
66
- * Hook for authentication state management
67
- *
68
- * Uses centralized Zustand store - all components share the same state.
69
- * Must call initializeAuthListener() once in app root.
70
- */
71
48
  export function useAuth(): UseAuthResult {
72
- // State from store - using typed selectors
73
49
  const user = useAuthStore(selectUser);
74
50
  const loading = useAuthStore(selectLoading);
75
51
  const error = useAuthStore(selectError);
@@ -79,13 +55,10 @@ export function useAuth(): UseAuthResult {
79
55
  const isAnonymous = useAuthStore(selectIsAnonymous);
80
56
  const isAuthReady = useAuthStore(selectIsAuthReady);
81
57
  const isRegisteredUser = useAuthStore(selectIsRegisteredUser);
82
-
83
- // Actions from store - using typed selectors
84
58
  const setLoading = useAuthStore(selectSetLoading);
85
59
  const setError = useAuthStore(selectSetError);
86
60
  const setIsAnonymous = useAuthStore(selectSetIsAnonymous);
87
61
 
88
- // Mutations
89
62
  const signInMutation = useSignInMutation();
90
63
  const signUpMutation = useSignUpMutation();
91
64
  const signOutMutation = useSignOutMutation();
@@ -99,8 +72,7 @@ export function useAuth(): UseAuthResult {
99
72
  await signUpMutation.mutateAsync({ email, password, displayName });
100
73
  setIsAnonymous(false);
101
74
  } catch (err: unknown) {
102
- const errorMessage = err instanceof Error ? err.message : "Sign up failed";
103
- setError(errorMessage);
75
+ setError(err instanceof Error ? err.message : "Sign up failed");
104
76
  throw err;
105
77
  } finally {
106
78
  setLoading(false);
@@ -117,8 +89,7 @@ export function useAuth(): UseAuthResult {
117
89
  await signInMutation.mutateAsync({ email, password });
118
90
  setIsAnonymous(false);
119
91
  } catch (err: unknown) {
120
- const errorMessage = err instanceof Error ? err.message : "Sign in failed";
121
- setError(errorMessage);
92
+ setError(err instanceof Error ? err.message : "Sign in failed");
122
93
  throw err;
123
94
  } finally {
124
95
  setLoading(false);
@@ -133,8 +104,7 @@ export function useAuth(): UseAuthResult {
133
104
  setError(null);
134
105
  await signOutMutation.mutateAsync();
135
106
  } catch (err: unknown) {
136
- const errorMessage = err instanceof Error ? err.message : "Sign out failed";
137
- setError(errorMessage);
107
+ setError(err instanceof Error ? err.message : "Sign out failed");
138
108
  throw err;
139
109
  } finally {
140
110
  setLoading(false);
@@ -147,7 +117,6 @@ export function useAuth(): UseAuthResult {
147
117
  await anonymousModeMutation.mutateAsync();
148
118
  setIsAnonymous(true);
149
119
  } catch {
150
- // Silently fail - anonymous mode is optional
151
120
  setIsAnonymous(true);
152
121
  } finally {
153
122
  setLoading(false);
@@ -155,19 +124,7 @@ export function useAuth(): UseAuthResult {
155
124
  }, [setIsAnonymous, setLoading, anonymousModeMutation]);
156
125
 
157
126
  return {
158
- user,
159
- userId,
160
- userType,
161
- loading,
162
- isAuthReady,
163
- isAnonymous,
164
- isAuthenticated,
165
- isRegisteredUser,
166
- error,
167
- signUp,
168
- signIn,
169
- signOut,
170
- continueAnonymously,
171
- setError,
127
+ user, userId, userType, loading, isAuthReady, isAnonymous, isAuthenticated, isRegisteredUser, error,
128
+ signUp, signIn, signOut, continueAnonymously, setError,
172
129
  };
173
130
  }
@@ -1,19 +1,10 @@
1
1
  /**
2
2
  * useGoogleAuth Hook
3
3
  * Handles Google OAuth flow using expo-auth-session and Firebase auth
4
- *
5
- * This hook provides complete Google sign-in flow:
6
- * 1. OAuth flow via expo-auth-session
7
- * 2. Firebase authentication with the obtained token
8
4
  */
9
5
 
10
6
  import { useState, useCallback, useEffect } from "react";
11
- import {
12
- useSocialAuth,
13
- type SocialAuthConfig,
14
- type SocialAuthResult,
15
- } from "@umituz/react-native-firebase";
16
-
7
+ import { useSocialAuth, type SocialAuthConfig, type SocialAuthResult } from "@umituz/react-native-firebase";
17
8
  import * as Google from "expo-auth-session/providers/google";
18
9
  import * as WebBrowser from "expo-web-browser";
19
10
 
@@ -34,38 +25,25 @@ export interface UseGoogleAuthResult {
34
25
 
35
26
  const PLACEHOLDER_CLIENT_ID = "000000000000-placeholder.apps.googleusercontent.com";
36
27
 
37
- /**
38
- * Validate Google auth config
39
- */
40
28
  function validateGoogleConfig(config?: GoogleAuthConfig): boolean {
41
29
  if (!config) return false;
42
-
43
- const hasValidClientId =
44
- !!(config.iosClientId && config.iosClientId !== PLACEHOLDER_CLIENT_ID) ||
45
- !!(config.webClientId && config.webClientId !== PLACEHOLDER_CLIENT_ID) ||
46
- !!(config.androidClientId && config.androidClientId !== PLACEHOLDER_CLIENT_ID);
47
-
48
- return hasValidClientId;
30
+ return !!(
31
+ (config.iosClientId && config.iosClientId !== PLACEHOLDER_CLIENT_ID) ||
32
+ (config.webClientId && config.webClientId !== PLACEHOLDER_CLIENT_ID) ||
33
+ (config.androidClientId && config.androidClientId !== PLACEHOLDER_CLIENT_ID)
34
+ );
49
35
  }
50
36
 
51
- /**
52
- * Hook for Google authentication with expo-auth-session
53
- */
54
37
  export function useGoogleAuth(config?: GoogleAuthConfig): UseGoogleAuthResult {
55
38
  const [isLoading, setIsLoading] = useState(false);
56
39
  const [googleError, setGoogleError] = useState<string | null>(null);
57
-
58
40
  const googleConfigured = validateGoogleConfig(config);
59
41
 
60
- const socialAuthConfig: SocialAuthConfig = {
42
+ const { signInWithGoogleToken, googleLoading: firebaseLoading } = useSocialAuth({
61
43
  google: config,
62
44
  apple: { enabled: false },
63
- };
64
-
65
- const { signInWithGoogleToken, googleLoading: firebaseLoading } =
66
- useSocialAuth(socialAuthConfig);
45
+ } as SocialAuthConfig);
67
46
 
68
- // Use Google auth request if available
69
47
  const authRequest = Google?.useAuthRequest({
70
48
  iosClientId: config?.iosClientId || PLACEHOLDER_CLIENT_ID,
71
49
  webClientId: config?.webClientId || PLACEHOLDER_CLIENT_ID,
@@ -76,7 +54,6 @@ export function useGoogleAuth(config?: GoogleAuthConfig): UseGoogleAuthResult {
76
54
  const googleResponse = authRequest?.[1] ?? null;
77
55
  const promptGoogleAsync = authRequest?.[2];
78
56
 
79
- // Handle Google OAuth response
80
57
  useEffect(() => {
81
58
  if (googleResponse?.type === "success") {
82
59
  const idToken = googleResponse.authentication?.idToken;
@@ -84,13 +61,8 @@ export function useGoogleAuth(config?: GoogleAuthConfig): UseGoogleAuthResult {
84
61
  setIsLoading(true);
85
62
  setGoogleError(null);
86
63
  signInWithGoogleToken(idToken)
87
- .catch((error) => {
88
- const errorMessage = error instanceof Error ? error.message : "Firebase sign-in failed";
89
- setGoogleError(errorMessage);
90
- })
91
- .finally(() => {
92
- setIsLoading(false);
93
- });
64
+ .catch((error) => { setGoogleError(error instanceof Error ? error.message : "Firebase sign-in failed"); })
65
+ .finally(() => { setIsLoading(false); });
94
66
  }
95
67
  } else if (googleResponse?.type === "error") {
96
68
  setGoogleError("Google authentication failed");
@@ -123,10 +95,7 @@ export function useGoogleAuth(config?: GoogleAuthConfig): UseGoogleAuthResult {
123
95
  const result = await promptGoogleAsync();
124
96
 
125
97
  if (result.type === "success" && result.authentication?.idToken) {
126
- const firebaseResult = await signInWithGoogleToken(
127
- result.authentication.idToken,
128
- );
129
- return firebaseResult;
98
+ return await signInWithGoogleToken(result.authentication.idToken);
130
99
  }
131
100
 
132
101
  if (result.type === "cancel") {
@@ -141,10 +110,7 @@ export function useGoogleAuth(config?: GoogleAuthConfig): UseGoogleAuthResult {
141
110
  } catch (error) {
142
111
  const errorMessage = error instanceof Error ? error.message : "Google sign-in failed";
143
112
  setGoogleError(errorMessage);
144
- return {
145
- success: false,
146
- error: errorMessage,
147
- };
113
+ return { success: false, error: errorMessage };
148
114
  } finally {
149
115
  setIsLoading(false);
150
116
  }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Auth Operation Utilities
3
+ * Shared error handling for authentication operations
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import type { MutationFunction } from "@tanstack/react-query";
8
+
9
+ export interface AuthOperationOptions {
10
+ setLoading: (loading: boolean) => void;
11
+ setError: (error: string | null) => void;
12
+ onSuccess?: () => void;
13
+ }
14
+
15
+ /**
16
+ * Create an auth operation wrapper with consistent error handling
17
+ */
18
+ export function createAuthOperation<T>(
19
+ mutation: MutationFunction<unknown, T>,
20
+ options: AuthOperationOptions
21
+ ) {
22
+ const { setLoading, setError, onSuccess } = options;
23
+
24
+ return useCallback(
25
+ async (params: T) => {
26
+ try {
27
+ setLoading(true);
28
+ setError(null);
29
+ await mutation.mutateAsync(params);
30
+ onSuccess?.();
31
+ } catch (err: unknown) {
32
+ const errorMessage = err instanceof Error ? err.message : "Operation failed";
33
+ setError(errorMessage);
34
+ throw err;
35
+ } finally {
36
+ setLoading(false);
37
+ }
38
+ },
39
+ [setLoading, setError, onSuccess, mutation]
40
+ );
41
+ }
42
+
43
+ /**
44
+ * Create auth operation that doesn't throw on failure
45
+ */
46
+ export function createSilentAuthOperation<T>(
47
+ mutation: MutationFunction<unknown, T>,
48
+ options: AuthOperationOptions
49
+ ) {
50
+ const { setLoading, setError, onSuccess } = options;
51
+
52
+ return useCallback(
53
+ async (params?: T) => {
54
+ try {
55
+ setLoading(true);
56
+ setError(null);
57
+ await mutation.mutateAsync(params);
58
+ onSuccess?.();
59
+ } catch {
60
+ // Silently fail
61
+ onSuccess?.();
62
+ } finally {
63
+ setLoading(false);
64
+ }
65
+ },
66
+ [setLoading, setError, onSuccess, mutation]
67
+ );
68
+ }
@@ -39,16 +39,7 @@ export interface ProfileFormValues {
39
39
  email: string;
40
40
  }
41
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 {
42
+ export function validateLoginForm(values: LoginFormValues, getErrorMessage: (key: string) => string): FormValidationResult {
52
43
  const errors: FormValidationError[] = [];
53
44
 
54
45
  const emailResult = validateEmail(values.email.trim());
@@ -61,19 +52,9 @@ export function validateLoginForm(
61
52
  errors.push({ field: "password", message: getErrorMessage(passwordResult.error) });
62
53
  }
63
54
 
64
- return {
65
- isValid: errors.length === 0,
66
- errors,
67
- };
55
+ return { isValid: errors.length === 0, errors };
68
56
  }
69
57
 
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
58
  export function validateRegisterForm(
78
59
  values: RegisterFormValues,
79
60
  getErrorMessage: (key: string) => string,
@@ -96,17 +77,9 @@ export function validateRegisterForm(
96
77
  errors.push({ field: "confirmPassword", message: getErrorMessage(confirmResult.error) });
97
78
  }
98
79
 
99
- return {
100
- isValid: errors.length === 0,
101
- errors,
102
- };
80
+ return { isValid: errors.length === 0, errors };
103
81
  }
104
82
 
105
- /**
106
- * Validate profile form fields
107
- * @param values - Form values to validate
108
- * @returns Validation result
109
- */
110
83
  export function validateProfileForm(values: ProfileFormValues): FormValidationResult {
111
84
  const errors: FormValidationError[] = [];
112
85
 
@@ -121,20 +94,10 @@ export function validateProfileForm(values: ProfileFormValues): FormValidationRe
121
94
  }
122
95
  }
123
96
 
124
- return {
125
- isValid: errors.length === 0,
126
- errors,
127
- };
97
+ return { isValid: errors.length === 0, errors };
128
98
  }
129
99
 
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> {
100
+ export function errorsToFieldErrors(errors: FormValidationError[]): Record<string, string> {
138
101
  const result: Record<string, string> = {};
139
102
  for (const error of errors) {
140
103
  result[error.field] = error.message;
@@ -142,32 +105,13 @@ export function errorsToFieldErrors(
142
105
  return result;
143
106
  }
144
107
 
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
108
  export function useFormValidation(getErrorMessage: (key: string) => string) {
151
- const validateLogin = useCallback(
152
- (values: LoginFormValues) => validateLoginForm(values, getErrorMessage),
153
- [getErrorMessage]
154
- );
155
-
109
+ const validateLogin = useCallback((values: LoginFormValues) => validateLoginForm(values, getErrorMessage), [getErrorMessage]);
156
110
  const validateRegister = useCallback(
157
- (values: RegisterFormValues, passwordConfig: PasswordConfig) =>
158
- validateRegisterForm(values, getErrorMessage, passwordConfig),
111
+ (values: RegisterFormValues, passwordConfig: PasswordConfig) => validateRegisterForm(values, getErrorMessage, passwordConfig),
159
112
  [getErrorMessage]
160
113
  );
114
+ const validateProfile = useCallback((values: ProfileFormValues) => validateProfileForm(values), []);
161
115
 
162
- const validateProfile = useCallback(
163
- (values: ProfileFormValues) => validateProfileForm(values),
164
- []
165
- );
166
-
167
- return {
168
- validateLogin,
169
- validateRegister,
170
- validateProfile,
171
- errorsToFieldErrors,
172
- };
116
+ return { validateLogin, validateRegister, validateProfile, errorsToFieldErrors };
173
117
  }