@umituz/react-native-auth 4.3.69 → 4.3.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.ts +1 -11
- package/src/infrastructure/services/AnonymousModeService.ts +9 -3
- package/src/infrastructure/services/AuthEventService.ts +3 -1
- package/src/infrastructure/services/AuthService.ts +3 -1
- package/src/infrastructure/utils/authStateHandler.ts +3 -1
- package/src/infrastructure/utils/calculators/index.ts +0 -6
- package/src/infrastructure/utils/calculators/passwordStrengthCalculator.ts +6 -34
- package/src/infrastructure/utils/calculators/userProfileCalculator.ts +0 -29
- package/src/infrastructure/utils/listener/authListenerStateHandler.ts +3 -1
- package/src/infrastructure/utils/listener/listenerState.util.ts +3 -1
- package/src/infrastructure/utils/listener/setupListener.ts +10 -4
- package/src/infrastructure/utils/safeCallback.ts +12 -4
- package/src/infrastructure/utils/validation/types.ts +7 -11
- package/src/init/createAuthInitModule.ts +9 -3
- package/src/presentation/components/AccountActions.tsx +19 -8
- package/src/presentation/components/AuthErrorDisplay.tsx +6 -5
- package/src/presentation/components/AuthHeader.tsx +11 -3
- package/src/presentation/components/AuthLink.tsx +7 -12
- package/src/presentation/components/PasswordMatchIndicator.tsx +5 -6
- package/src/presentation/components/PasswordStrengthIndicator.tsx +14 -17
- package/src/presentation/components/SocialLoginButtons.tsx +17 -12
- package/src/presentation/hooks/README.md +0 -57
- package/src/presentation/hooks/useAccountManagement.md +0 -1
- package/src/presentation/hooks/useLoginForm.ts +10 -1
- package/src/presentation/hooks/usePasswordPromptNavigation.ts +3 -1
- package/src/presentation/providers/AuthProvider.tsx +3 -1
- package/src/presentation/screens/AccountScreen.tsx +6 -3
- package/src/presentation/screens/LoginScreen.tsx +14 -5
- package/src/presentation/screens/RegisterScreen.tsx +14 -11
- package/src/presentation/stores/authModalStore.ts +6 -2
- package/src/presentation/utils/passwordPromptCallback.ts +3 -1
- package/src/presentation/hooks/useProfileEdit.ts +0 -83
- package/src/presentation/hooks/useSocialLogin.md +0 -381
- package/src/presentation/hooks/useSocialLogin.ts +0 -95
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-auth",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.72",
|
|
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
|
@@ -32,7 +32,7 @@ export { initializeAuth, isAuthInitialized, resetAuthInitialization } from './in
|
|
|
32
32
|
export type { InitializeAuthOptions } from './infrastructure/services/initializeAuth';
|
|
33
33
|
export { validateEmail, validatePasswordForLogin, validatePasswordForRegister, validatePasswordConfirmation, validateDisplayName } from './infrastructure/utils/AuthValidation';
|
|
34
34
|
export type { ValidationResult, PasswordStrengthResult, PasswordRequirements } from './infrastructure/utils/AuthValidation';
|
|
35
|
-
export type {
|
|
35
|
+
export type { FormValidationError, FormValidationResult } from './infrastructure/utils/validation/types';
|
|
36
36
|
export { SECURITY_LIMITS, sanitizeEmail, sanitizePassword, sanitizeName } from './infrastructure/utils/validation/sanitization';
|
|
37
37
|
export type { SecurityLimitKey } from './infrastructure/utils/validation/sanitization';
|
|
38
38
|
export { isEmpty, isEmptyEmail, isEmptyPassword, isEmptyName, isNotEmpty, hasContent } from './infrastructure/utils/validation/validationHelpers';
|
|
@@ -59,16 +59,10 @@ export {
|
|
|
59
59
|
getFirstErrorMessage,
|
|
60
60
|
// User Profile Calculator
|
|
61
61
|
calculateUserProfileDisplay,
|
|
62
|
-
calculateDisplayName,
|
|
63
|
-
hasUserAvatar,
|
|
64
|
-
getAvatarUrl,
|
|
65
62
|
// Password Strength Calculator
|
|
66
63
|
calculatePasswordRequirements,
|
|
67
64
|
calculatePasswordsMatch,
|
|
68
|
-
calculateConfirmationError,
|
|
69
|
-
calculatePasswordValidity,
|
|
70
65
|
calculatePasswordValidation,
|
|
71
|
-
hasMinLength,
|
|
72
66
|
calculatePasswordStrength,
|
|
73
67
|
} from './infrastructure/utils/calculators';
|
|
74
68
|
|
|
@@ -89,10 +83,6 @@ export { useUserProfile } from './presentation/hooks/useUserProfile';
|
|
|
89
83
|
export type { UserProfileData, UseUserProfileParams } from './presentation/hooks/useUserProfile';
|
|
90
84
|
export { useAccountManagement } from './presentation/hooks/useAccountManagement';
|
|
91
85
|
export type { UseAccountManagementReturn, UseAccountManagementOptions } from './presentation/hooks/useAccountManagement';
|
|
92
|
-
export { useProfileEdit } from './presentation/hooks/useProfileEdit';
|
|
93
|
-
export type { ProfileEditFormState, UseProfileEditReturn } from './presentation/hooks/useProfileEdit';
|
|
94
|
-
export { useSocialLogin } from './presentation/hooks/useSocialLogin';
|
|
95
|
-
export type { UseSocialLoginConfig, UseSocialLoginResult } from './presentation/hooks/useSocialLogin';
|
|
96
86
|
export { useAppleAuth } from './presentation/hooks/useAppleAuth';
|
|
97
87
|
export type { UseAppleAuthResult } from './presentation/hooks/useAppleAuth';
|
|
98
88
|
export { useAuthBottomSheet } from './presentation/hooks/useAuthBottomSheet';
|
|
@@ -20,7 +20,9 @@ export class AnonymousModeService {
|
|
|
20
20
|
this.isAnonymousMode = value === "true";
|
|
21
21
|
return this.isAnonymousMode;
|
|
22
22
|
} catch (error) {
|
|
23
|
-
|
|
23
|
+
if (__DEV__) {
|
|
24
|
+
console.error('[AnonymousModeService] Failed to load state:', error instanceof Error ? error.message : String(error));
|
|
25
|
+
}
|
|
24
26
|
this.isAnonymousMode = false;
|
|
25
27
|
return false;
|
|
26
28
|
}
|
|
@@ -31,7 +33,9 @@ export class AnonymousModeService {
|
|
|
31
33
|
await storageProvider.set(this.storageKey, value.toString());
|
|
32
34
|
return true;
|
|
33
35
|
} catch (error) {
|
|
34
|
-
|
|
36
|
+
if (__DEV__) {
|
|
37
|
+
console.error('[AnonymousModeService] Failed to save state:', error instanceof Error ? error.message : String(error));
|
|
38
|
+
}
|
|
35
39
|
return false;
|
|
36
40
|
}
|
|
37
41
|
}
|
|
@@ -42,7 +46,9 @@ export class AnonymousModeService {
|
|
|
42
46
|
this.isAnonymousMode = false;
|
|
43
47
|
return true;
|
|
44
48
|
} catch (error) {
|
|
45
|
-
|
|
49
|
+
if (__DEV__) {
|
|
50
|
+
console.error('[AnonymousModeService] Failed to clear state:', error instanceof Error ? error.message : String(error));
|
|
51
|
+
}
|
|
46
52
|
return false;
|
|
47
53
|
}
|
|
48
54
|
}
|
|
@@ -101,7 +101,9 @@ class AuthEventService {
|
|
|
101
101
|
try {
|
|
102
102
|
listener(payload);
|
|
103
103
|
} catch (error) {
|
|
104
|
-
|
|
104
|
+
if (__DEV__) {
|
|
105
|
+
console.error(`[AuthEventService] Listener error for "${event}":`, error);
|
|
106
|
+
}
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
109
|
}
|
|
@@ -82,7 +82,9 @@ export class AuthService {
|
|
|
82
82
|
if (this.anonymousModeService.getIsAnonymousMode() && this.storageProvider) {
|
|
83
83
|
const success = await this.anonymousModeService.clear(this.storageProvider);
|
|
84
84
|
if (!success) {
|
|
85
|
-
|
|
85
|
+
if (__DEV__) {
|
|
86
|
+
console.warn('[AuthService] Failed to clear anonymous mode from storage');
|
|
87
|
+
}
|
|
86
88
|
// Force clear in memory to maintain consistency and prevent stale state
|
|
87
89
|
// Storage clear failure shouldn't block auth flow, so we force update memory
|
|
88
90
|
this.anonymousModeService.setAnonymousMode(false);
|
|
@@ -49,7 +49,9 @@ export function createAuthStateHandler(
|
|
|
49
49
|
try {
|
|
50
50
|
await ensureUserDocument(user, extras);
|
|
51
51
|
} catch (error) {
|
|
52
|
-
|
|
52
|
+
if (__DEV__) {
|
|
53
|
+
console.error('[AuthStateHandler] Failed to ensure user document:', error);
|
|
54
|
+
}
|
|
53
55
|
// Continue execution - don't let user document creation failure block auth flow
|
|
54
56
|
}
|
|
55
57
|
|
|
@@ -25,18 +25,12 @@ export {
|
|
|
25
25
|
// User Profile Calculator
|
|
26
26
|
export {
|
|
27
27
|
calculateUserProfileDisplay,
|
|
28
|
-
calculateDisplayName,
|
|
29
|
-
hasUserAvatar,
|
|
30
|
-
getAvatarUrl,
|
|
31
28
|
} from "./userProfileCalculator";
|
|
32
29
|
|
|
33
30
|
// Password Strength Calculator
|
|
34
31
|
export {
|
|
35
32
|
calculatePasswordRequirements,
|
|
36
33
|
calculatePasswordsMatch,
|
|
37
|
-
calculateConfirmationError,
|
|
38
|
-
calculatePasswordValidity,
|
|
39
34
|
calculatePasswordValidation,
|
|
40
|
-
hasMinLength,
|
|
41
35
|
calculatePasswordStrength,
|
|
42
36
|
} from "./passwordStrengthCalculator";
|
|
@@ -50,31 +50,6 @@ export function calculatePasswordsMatch(
|
|
|
50
50
|
return !!(password && confirmPassword && password === confirmPassword);
|
|
51
51
|
}
|
|
52
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
53
|
/**
|
|
79
54
|
* Calculate all password validation state at once
|
|
80
55
|
* More efficient than calling individual functions
|
|
@@ -91,10 +66,14 @@ export function calculatePasswordValidation(
|
|
|
91
66
|
const passwordsMatch = calculatePasswordsMatch(password, confirmPassword);
|
|
92
67
|
|
|
93
68
|
// Calculate confirmation error
|
|
94
|
-
|
|
69
|
+
let confirmationError: string | null = null;
|
|
70
|
+
if (confirmPassword) {
|
|
71
|
+
const result = validatePasswordConfirmation(password, confirmPassword);
|
|
72
|
+
confirmationError = result.error ?? null;
|
|
73
|
+
}
|
|
95
74
|
|
|
96
75
|
// Calculate overall validity
|
|
97
|
-
const isValid =
|
|
76
|
+
const isValid = requirements.hasMinLength && passwordsMatch;
|
|
98
77
|
|
|
99
78
|
return {
|
|
100
79
|
requirements,
|
|
@@ -104,13 +83,6 @@ export function calculatePasswordValidation(
|
|
|
104
83
|
};
|
|
105
84
|
}
|
|
106
85
|
|
|
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
86
|
/**
|
|
115
87
|
* Calculate password strength score (0-100)
|
|
116
88
|
* Can be extended for more sophisticated strength calculation
|
|
@@ -46,32 +46,3 @@ export function calculateUserProfileDisplay(
|
|
|
46
46
|
accountSettingsRoute: accountRoute,
|
|
47
47
|
};
|
|
48
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
|
-
}
|
|
@@ -42,7 +42,9 @@ export function handleAuthStateChange(
|
|
|
42
42
|
// Call user callback with proper error handling for async callbacks
|
|
43
43
|
safeCallbackSync(onAuthStateChange, [user], '[AuthListener]');
|
|
44
44
|
} catch (error) {
|
|
45
|
-
|
|
45
|
+
if (__DEV__) {
|
|
46
|
+
console.error("[AuthListener] Error handling auth state change:", error);
|
|
47
|
+
}
|
|
46
48
|
// Ensure we don't leave the app in a bad state
|
|
47
49
|
store.setInitialized(true);
|
|
48
50
|
store.setLoading(false);
|
|
@@ -119,7 +119,9 @@ export function resetListenerState(): void {
|
|
|
119
119
|
try {
|
|
120
120
|
state.unsubscribe();
|
|
121
121
|
} catch (error) {
|
|
122
|
-
|
|
122
|
+
if (__DEV__) {
|
|
123
|
+
console.error('[ListenerState] Error during unsubscribe:', error);
|
|
124
|
+
}
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
state.initialized = false;
|
|
@@ -10,6 +10,8 @@ import { getAuthService } from "../../services/AuthService";
|
|
|
10
10
|
import { completeInitialization, setUnsubscribe } from "./listenerState.util";
|
|
11
11
|
import { handleAuthStateChange } from "./authListenerStateHandler";
|
|
12
12
|
|
|
13
|
+
const AUTH_LISTENER_TIMEOUT_MS = 10000;
|
|
14
|
+
|
|
13
15
|
type StoreActions = AuthActions;
|
|
14
16
|
|
|
15
17
|
/**
|
|
@@ -32,15 +34,17 @@ export function setupAuthListener(
|
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
// Safety timeout: if listener doesn't trigger within
|
|
37
|
+
// Safety timeout: if listener doesn't trigger within AUTH_LISTENER_TIMEOUT_MS, mark as initialized
|
|
36
38
|
let hasTriggered = false;
|
|
37
39
|
const timeout = setTimeout(() => {
|
|
38
40
|
if (!hasTriggered) {
|
|
39
|
-
|
|
41
|
+
if (__DEV__) {
|
|
42
|
+
console.warn("[AuthListener] Auth listener timeout - marking as initialized");
|
|
43
|
+
}
|
|
40
44
|
store.setInitialized(true);
|
|
41
45
|
store.setLoading(false);
|
|
42
46
|
}
|
|
43
|
-
},
|
|
47
|
+
}, AUTH_LISTENER_TIMEOUT_MS);
|
|
44
48
|
|
|
45
49
|
try {
|
|
46
50
|
const unsubscribe = onIdTokenChanged(auth, (user) => {
|
|
@@ -55,7 +59,9 @@ export function setupAuthListener(
|
|
|
55
59
|
} catch (error) {
|
|
56
60
|
clearTimeout(timeout);
|
|
57
61
|
// If listener setup fails, ensure we clean up and mark as initialized
|
|
58
|
-
|
|
62
|
+
if (__DEV__) {
|
|
63
|
+
console.error("[AuthListener] Failed to setup auth listener:", error);
|
|
64
|
+
}
|
|
59
65
|
completeInitialization();
|
|
60
66
|
store.setLoading(false);
|
|
61
67
|
store.setInitialized(true);
|
|
@@ -34,12 +34,16 @@ export async function safeCallback<T extends unknown[]>(
|
|
|
34
34
|
// If callback returns a promise, await it and catch rejections
|
|
35
35
|
if (result && typeof result.then === 'function') {
|
|
36
36
|
await result.catch((error) => {
|
|
37
|
-
|
|
37
|
+
if (__DEV__) {
|
|
38
|
+
console.error(`${errorPrefix} User callback promise rejected:`, error);
|
|
39
|
+
}
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
} catch (error) {
|
|
41
43
|
// Catch synchronous errors
|
|
42
|
-
|
|
44
|
+
if (__DEV__) {
|
|
45
|
+
console.error(`${errorPrefix} User callback error:`, error);
|
|
46
|
+
}
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
49
|
|
|
@@ -70,11 +74,15 @@ export function safeCallbackSync<T extends unknown[]>(
|
|
|
70
74
|
// If callback returns a promise, catch rejections but don't await
|
|
71
75
|
if (result && typeof result.then === 'function') {
|
|
72
76
|
result.catch((error) => {
|
|
73
|
-
|
|
77
|
+
if (__DEV__) {
|
|
78
|
+
console.error(`${errorPrefix} User callback promise rejected:`, error);
|
|
79
|
+
}
|
|
74
80
|
});
|
|
75
81
|
}
|
|
76
82
|
} catch (error) {
|
|
77
83
|
// Catch synchronous errors
|
|
78
|
-
|
|
84
|
+
if (__DEV__) {
|
|
85
|
+
console.error(`${errorPrefix} User callback error:`, error);
|
|
86
|
+
}
|
|
79
87
|
}
|
|
80
88
|
}
|
|
@@ -3,19 +3,12 @@
|
|
|
3
3
|
* Shared type definitions for validation results across the application
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Base validation result
|
|
8
|
-
* Common interface for all validation results
|
|
9
|
-
*/
|
|
10
|
-
export interface BaseValidationResult {
|
|
11
|
-
isValid: boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
6
|
/**
|
|
15
7
|
* Single field validation result
|
|
16
8
|
* Used for validating individual fields (email, password, etc.)
|
|
17
9
|
*/
|
|
18
|
-
export interface ValidationResult
|
|
10
|
+
export interface ValidationResult {
|
|
11
|
+
isValid: boolean;
|
|
19
12
|
error?: string;
|
|
20
13
|
}
|
|
21
14
|
|
|
@@ -32,7 +25,8 @@ export interface FormValidationError {
|
|
|
32
25
|
* Multi-field form validation result
|
|
33
26
|
* Used for validating entire forms with multiple fields
|
|
34
27
|
*/
|
|
35
|
-
export interface FormValidationResult
|
|
28
|
+
export interface FormValidationResult {
|
|
29
|
+
isValid: boolean;
|
|
36
30
|
errors: FormValidationError[];
|
|
37
31
|
}
|
|
38
32
|
|
|
@@ -48,6 +42,8 @@ export interface PasswordRequirements {
|
|
|
48
42
|
* Password strength validation result
|
|
49
43
|
* Extends ValidationResult with password-specific requirements
|
|
50
44
|
*/
|
|
51
|
-
export interface PasswordStrengthResult
|
|
45
|
+
export interface PasswordStrengthResult {
|
|
46
|
+
isValid: boolean;
|
|
47
|
+
error?: string;
|
|
52
48
|
requirements: PasswordRequirements;
|
|
53
49
|
}
|
|
@@ -92,7 +92,9 @@ export function createAuthInitModule(
|
|
|
92
92
|
try {
|
|
93
93
|
await onRestorePurchases();
|
|
94
94
|
} catch (error) {
|
|
95
|
-
|
|
95
|
+
if (__DEV__) {
|
|
96
|
+
console.error('[AuthInitModule] Purchase restoration failed:', error instanceof Error ? error.message : String(error));
|
|
97
|
+
}
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
|
|
@@ -101,7 +103,9 @@ export function createAuthInitModule(
|
|
|
101
103
|
try {
|
|
102
104
|
await onUserConverted(anonymousId, authenticatedId);
|
|
103
105
|
} catch (error) {
|
|
104
|
-
|
|
106
|
+
if (__DEV__) {
|
|
107
|
+
console.error('[AuthInitModule] onUserConverted callback failed:', error instanceof Error ? error.message : String(error));
|
|
108
|
+
}
|
|
105
109
|
}
|
|
106
110
|
}
|
|
107
111
|
},
|
|
@@ -109,7 +113,9 @@ export function createAuthInitModule(
|
|
|
109
113
|
|
|
110
114
|
return true;
|
|
111
115
|
} catch (error) {
|
|
112
|
-
|
|
116
|
+
if (__DEV__) {
|
|
117
|
+
console.error('[AuthInitModule] Auth initialization failed:', error instanceof Error ? error.message : String(error));
|
|
118
|
+
}
|
|
113
119
|
throw error;
|
|
114
120
|
}
|
|
115
121
|
},
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Account Actions Component
|
|
3
|
+
* Logout and delete account actions
|
|
4
|
+
* PERFORMANCE: Memoized with useCallback for stable alert action handlers
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { memo, useCallback } from "react";
|
|
2
8
|
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
3
9
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
4
10
|
import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system/atoms";
|
|
@@ -26,7 +32,7 @@ interface AccountActionsProps {
|
|
|
26
32
|
config: AccountActionsConfig;
|
|
27
33
|
}
|
|
28
34
|
|
|
29
|
-
export const AccountActions
|
|
35
|
+
export const AccountActions = memo<AccountActionsProps>(({ config }) => {
|
|
30
36
|
const tokens = useAppDesignTokens();
|
|
31
37
|
const alert = useAlert();
|
|
32
38
|
const {
|
|
@@ -46,7 +52,8 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
|
|
|
46
52
|
showChangePassword = false,
|
|
47
53
|
} = config;
|
|
48
54
|
|
|
49
|
-
|
|
55
|
+
// PERFORMANCE: Stable callback references prevent unnecessary re-renders
|
|
56
|
+
const handleLogout = useCallback(() => {
|
|
50
57
|
alert.show(AlertType.WARNING, AlertMode.MODAL, logoutConfirmTitle, logoutConfirmMessage, {
|
|
51
58
|
actions: [
|
|
52
59
|
{ id: "cancel", label: cancelText, style: "secondary", onPress: () => {} },
|
|
@@ -58,15 +65,17 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
|
|
|
58
65
|
try {
|
|
59
66
|
await onLogout();
|
|
60
67
|
} catch (error) {
|
|
61
|
-
|
|
68
|
+
if (__DEV__) {
|
|
69
|
+
console.error('[AccountActions] Logout failed:', error instanceof Error ? error.message : String(error));
|
|
70
|
+
}
|
|
62
71
|
}
|
|
63
72
|
},
|
|
64
73
|
},
|
|
65
74
|
],
|
|
66
75
|
});
|
|
67
|
-
};
|
|
76
|
+
}, [alert, logoutText, logoutConfirmTitle, logoutConfirmMessage, cancelText, onLogout]);
|
|
68
77
|
|
|
69
|
-
const handleDeleteAccount = () => {
|
|
78
|
+
const handleDeleteAccount = useCallback(() => {
|
|
70
79
|
alert.show(AlertType.ERROR, AlertMode.MODAL, deleteConfirmTitle, deleteConfirmMessage, {
|
|
71
80
|
actions: [
|
|
72
81
|
{ id: "cancel", label: cancelText, style: "secondary", onPress: () => {} },
|
|
@@ -84,7 +93,7 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
|
|
|
84
93
|
},
|
|
85
94
|
],
|
|
86
95
|
});
|
|
87
|
-
};
|
|
96
|
+
}, [alert, deleteAccountText, deleteConfirmTitle, deleteConfirmMessage, deleteErrorTitle, deleteErrorMessage, cancelText, onDeleteAccount]);
|
|
88
97
|
|
|
89
98
|
return (
|
|
90
99
|
<View style={styles.container}>
|
|
@@ -109,7 +118,9 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
|
|
|
109
118
|
</TouchableOpacity>
|
|
110
119
|
</View>
|
|
111
120
|
);
|
|
112
|
-
};
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
AccountActions.displayName = 'AccountActions';
|
|
113
124
|
|
|
114
125
|
const styles = StyleSheet.create({
|
|
115
126
|
container: { gap: 12 },
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auth Error Display Component
|
|
3
3
|
* Displays authentication errors using design system tokens
|
|
4
|
+
* PERFORMANCE: Memoized to prevent unnecessary re-renders when parent updates
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import React from "react";
|
|
7
|
+
import React, { memo } from "react";
|
|
7
8
|
import { AlertInline, AlertService, AlertMode } from "@umituz/react-native-design-system/molecules";
|
|
8
9
|
|
|
9
10
|
interface AuthErrorDisplayProps {
|
|
10
11
|
error: string | null;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export const AuthErrorDisplay
|
|
14
|
-
error,
|
|
15
|
-
}) => {
|
|
14
|
+
export const AuthErrorDisplay = memo<AuthErrorDisplayProps>(({ error }) => {
|
|
16
15
|
const alert = React.useMemo(() => {
|
|
17
16
|
if (!error) return null;
|
|
18
17
|
return AlertService.createErrorAlert(error, undefined, {
|
|
@@ -25,7 +24,9 @@ export const AuthErrorDisplay: React.FC<AuthErrorDisplayProps> = ({
|
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
return <AlertInline alert={alert} />;
|
|
28
|
-
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
AuthErrorDisplay.displayName = 'AuthErrorDisplay';
|
|
29
30
|
|
|
30
31
|
|
|
31
32
|
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Auth Header Component
|
|
3
|
+
* Displays auth screen title and optional subtitle
|
|
4
|
+
* PERFORMANCE: Memoized to prevent unnecessary re-renders
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { memo } from "react";
|
|
2
8
|
import { View, StyleSheet } from "react-native";
|
|
3
9
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
4
10
|
import { AtomicText } from "@umituz/react-native-design-system/atoms";
|
|
@@ -8,7 +14,7 @@ interface AuthHeaderProps {
|
|
|
8
14
|
subtitle?: string;
|
|
9
15
|
}
|
|
10
16
|
|
|
11
|
-
export const AuthHeader
|
|
17
|
+
export const AuthHeader = memo<AuthHeaderProps>(({ title, subtitle }) => {
|
|
12
18
|
const tokens = useAppDesignTokens();
|
|
13
19
|
|
|
14
20
|
return (
|
|
@@ -34,7 +40,9 @@ export const AuthHeader: React.FC<AuthHeaderProps> = ({ title, subtitle }) => {
|
|
|
34
40
|
)}
|
|
35
41
|
</View>
|
|
36
42
|
);
|
|
37
|
-
};
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
AuthHeader.displayName = 'AuthHeader';
|
|
38
46
|
|
|
39
47
|
const styles = StyleSheet.create({
|
|
40
48
|
header: {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auth Link Component
|
|
3
3
|
* Link text with button for navigation between auth screens
|
|
4
|
+
* PERFORMANCE: Memoized to prevent unnecessary re-renders
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import React from "react";
|
|
7
|
+
import React, { memo } from "react";
|
|
7
8
|
import { View, StyleSheet } from "react-native";
|
|
8
9
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
9
10
|
import { AtomicText, AtomicButton } from "@umituz/react-native-design-system/atoms";
|
|
@@ -15,20 +16,12 @@ interface AuthLinkProps {
|
|
|
15
16
|
disabled?: boolean;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
export const AuthLink
|
|
19
|
-
text,
|
|
20
|
-
linkText,
|
|
21
|
-
onPress,
|
|
22
|
-
disabled = false,
|
|
23
|
-
}) => {
|
|
19
|
+
export const AuthLink = memo<AuthLinkProps>(({ text, linkText, onPress, disabled = false }) => {
|
|
24
20
|
const tokens = useAppDesignTokens();
|
|
25
21
|
|
|
26
22
|
return (
|
|
27
23
|
<View style={[styles.container, { marginTop: tokens.spacing.xs, paddingTop: tokens.spacing.xs }]}>
|
|
28
|
-
<AtomicText
|
|
29
|
-
type="bodyMedium"
|
|
30
|
-
color="textSecondary"
|
|
31
|
-
>
|
|
24
|
+
<AtomicText type="bodyMedium" color="textSecondary">
|
|
32
25
|
{text}{" "}
|
|
33
26
|
</AtomicText>
|
|
34
27
|
<AtomicButton
|
|
@@ -41,7 +34,9 @@ export const AuthLink: React.FC<AuthLinkProps> = ({
|
|
|
41
34
|
</AtomicButton>
|
|
42
35
|
</View>
|
|
43
36
|
);
|
|
44
|
-
};
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
AuthLink.displayName = 'AuthLink';
|
|
45
40
|
|
|
46
41
|
const styles = StyleSheet.create({
|
|
47
42
|
container: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { memo } from "react";
|
|
2
2
|
import { View, StyleSheet } from "react-native";
|
|
3
3
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
4
4
|
import { AtomicText } from "@umituz/react-native-design-system/atoms";
|
|
@@ -13,10 +13,7 @@ interface PasswordMatchIndicatorProps {
|
|
|
13
13
|
isMatch: boolean;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export const PasswordMatchIndicator
|
|
17
|
-
translations,
|
|
18
|
-
isMatch,
|
|
19
|
-
}) => {
|
|
16
|
+
export const PasswordMatchIndicator = memo<PasswordMatchIndicatorProps>(({ translations, isMatch }) => {
|
|
20
17
|
const tokens = useAppDesignTokens();
|
|
21
18
|
const color = isMatch ? tokens.colors.success : tokens.colors.error;
|
|
22
19
|
const text = isMatch ? translations.match : translations.noMatch;
|
|
@@ -27,7 +24,9 @@ export const PasswordMatchIndicator: React.FC<PasswordMatchIndicatorProps> = ({
|
|
|
27
24
|
<AtomicText type="labelSmall" style={{ color }}>{text}</AtomicText>
|
|
28
25
|
</View>
|
|
29
26
|
);
|
|
30
|
-
};
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
PasswordMatchIndicator.displayName = 'PasswordMatchIndicator';
|
|
31
30
|
|
|
32
31
|
const styles = StyleSheet.create({
|
|
33
32
|
container: {
|