@umituz/react-native-auth 4.3.68 → 4.3.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/package.json +1 -1
  2. package/src/index.ts +1 -11
  3. package/src/infrastructure/services/AnonymousModeService.ts +9 -3
  4. package/src/infrastructure/services/AuthEventService.ts +10 -4
  5. package/src/infrastructure/services/AuthService.ts +3 -1
  6. package/src/infrastructure/utils/authStateHandler.ts +3 -1
  7. package/src/infrastructure/utils/calculators/index.ts +0 -6
  8. package/src/infrastructure/utils/calculators/passwordStrengthCalculator.ts +6 -34
  9. package/src/infrastructure/utils/calculators/userProfileCalculator.ts +0 -29
  10. package/src/infrastructure/utils/listener/authListenerStateHandler.ts +3 -1
  11. package/src/infrastructure/utils/listener/listenerState.util.ts +3 -1
  12. package/src/infrastructure/utils/listener/setupListener.ts +10 -4
  13. package/src/infrastructure/utils/safeCallback.ts +12 -4
  14. package/src/infrastructure/utils/validation/types.ts +7 -11
  15. package/src/init/createAuthInitModule.ts +9 -3
  16. package/src/presentation/components/AccountActions.tsx +3 -1
  17. package/src/presentation/components/AuthErrorDisplay.tsx +6 -5
  18. package/src/presentation/components/PasswordMatchIndicator.tsx +5 -6
  19. package/src/presentation/components/PasswordStrengthIndicator.tsx +14 -17
  20. package/src/presentation/hooks/README.md +0 -57
  21. package/src/presentation/hooks/useAccountManagement.md +0 -1
  22. package/src/presentation/hooks/useLoginForm.ts +10 -1
  23. package/src/presentation/hooks/usePasswordPromptNavigation.ts +3 -1
  24. package/src/presentation/providers/AuthProvider.tsx +3 -1
  25. package/src/presentation/stores/authModalStore.ts +6 -2
  26. package/src/presentation/utils/passwordPromptCallback.ts +3 -1
  27. package/src/presentation/hooks/useProfileEdit.ts +0 -83
  28. package/src/presentation/hooks/useSocialLogin.md +0 -381
  29. 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.68",
3
+ "version": "4.3.71",
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 { BaseValidationResult, FormValidationError, FormValidationResult } from './infrastructure/utils/validation/types';
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
- console.error('[AnonymousModeService] Failed to load state:', error instanceof Error ? error.message : String(error));
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
- console.error('[AnonymousModeService] Failed to save state:', error instanceof Error ? error.message : String(error));
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
- console.error('[AnonymousModeService] Failed to clear state:', error instanceof Error ? error.message : String(error));
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
  }
@@ -68,11 +68,15 @@ class AuthEventService {
68
68
 
69
69
  // Return cleanup function
70
70
  return () => {
71
- const index = eventListeners.indexOf(listener);
71
+ // Re-fetch eventListeners to ensure we have the current array
72
+ const currentListeners = this.listeners.get(event);
73
+ if (!currentListeners) return;
74
+
75
+ const index = currentListeners.indexOf(listener);
72
76
  if (index > -1) {
73
- eventListeners.splice(index, 1);
77
+ currentListeners.splice(index, 1);
74
78
  // Clean up empty arrays to prevent memory leaks
75
- if (eventListeners.length === 0) {
79
+ if (currentListeners.length === 0) {
76
80
  this.listeners.delete(event);
77
81
  }
78
82
  }
@@ -97,7 +101,9 @@ class AuthEventService {
97
101
  try {
98
102
  listener(payload);
99
103
  } catch (error) {
100
- console.error(`[AuthEventService] Listener error for "${event}":`, error);
104
+ if (__DEV__) {
105
+ console.error(`[AuthEventService] Listener error for "${event}":`, error);
106
+ }
101
107
  }
102
108
  }
103
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
- console.warn('[AuthService] Failed to clear anonymous mode from storage');
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
- console.error('[AuthStateHandler] Failed to ensure user document:', error);
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
- const confirmationError = calculateConfirmationError(password, confirmPassword);
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 = calculatePasswordValidity(requirements, passwordsMatch);
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
- console.error("[AuthListener] Error handling auth state change:", error);
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
- console.error('[ListenerState] Error during unsubscribe:', error);
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 10 seconds, mark as initialized
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
- console.warn("[AuthListener] Auth listener timeout - marking as initialized");
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
- }, 10000);
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
- console.error("[AuthListener] Failed to setup auth listener:", error);
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
- console.error(`${errorPrefix} User callback promise rejected:`, error);
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
- console.error(`${errorPrefix} User callback error:`, error);
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
- console.error(`${errorPrefix} User callback promise rejected:`, error);
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
- console.error(`${errorPrefix} User callback error:`, error);
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 extends BaseValidationResult {
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 extends BaseValidationResult {
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 extends ValidationResult {
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
- console.error('[AuthInitModule] Purchase restoration failed:', error instanceof Error ? error.message : String(error));
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
- console.error('[AuthInitModule] onUserConverted callback failed:', error instanceof Error ? error.message : String(error));
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
- console.error('[AuthInitModule] Auth initialization failed:', error instanceof Error ? error.message : String(error));
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
  },
@@ -58,7 +58,9 @@ export const AccountActions: React.FC<AccountActionsProps> = ({ config }) => {
58
58
  try {
59
59
  await onLogout();
60
60
  } catch (error) {
61
- console.error('[AccountActions] Logout failed:', error instanceof Error ? error.message : String(error));
61
+ if (__DEV__) {
62
+ console.error('[AccountActions] Logout failed:', error instanceof Error ? error.message : String(error));
63
+ }
62
64
  }
63
65
  },
64
66
  },
@@ -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: React.FC<AuthErrorDisplayProps> = ({
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,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: React.FC<PasswordMatchIndicatorProps> = ({
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: {
@@ -1,4 +1,4 @@
1
- import React from "react";
1
+ import React, { useMemo, 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";
@@ -22,12 +22,7 @@ interface RequirementDotProps {
22
22
  pendingColor: ColorVariant;
23
23
  }
24
24
 
25
- const RequirementDot: React.FC<RequirementDotProps> = ({
26
- label,
27
- isValid,
28
- successColor,
29
- pendingColor,
30
- }) => {
25
+ const RequirementDot = memo<RequirementDotProps>(({ label, isValid, successColor, pendingColor }) => {
31
26
  const tokens = useAppDesignTokens();
32
27
  const colorKey = isValid ? successColor : pendingColor;
33
28
  const dotColor = (tokens.colors as Record<string, string>)[colorKey] || tokens.colors.textTertiary;
@@ -40,20 +35,20 @@ const RequirementDot: React.FC<RequirementDotProps> = ({
40
35
  </AtomicText>
41
36
  </View>
42
37
  );
43
- };
38
+ });
39
+
40
+ RequirementDot.displayName = 'RequirementDot';
44
41
 
45
- export const PasswordStrengthIndicator: React.FC<PasswordStrengthIndicatorProps> = ({
46
- translations,
47
- requirements,
48
- showLabels = true,
49
- }) => {
42
+ export const PasswordStrengthIndicator = memo<PasswordStrengthIndicatorProps>(({ translations, requirements, showLabels = true }) => {
50
43
  const tokens = useAppDesignTokens();
51
44
  const successColor: ColorVariant = "success";
52
45
  const pendingColor: ColorVariant = "textTertiary";
53
46
 
54
- const items = [
55
- { key: "minLength", label: translations.minLength, isValid: requirements.hasMinLength },
56
- ];
47
+ // PERFORMANCE: Memoize items array to prevent recreation on every render
48
+ const items = useMemo(
49
+ () => [{ key: "minLength" as const, label: translations.minLength, isValid: requirements.hasMinLength }],
50
+ [translations.minLength, requirements.hasMinLength]
51
+ );
57
52
 
58
53
  if (!showLabels) {
59
54
  return (
@@ -86,7 +81,9 @@ export const PasswordStrengthIndicator: React.FC<PasswordStrengthIndicatorProps>
86
81
  ))}
87
82
  </View>
88
83
  );
89
- };
84
+ });
85
+
86
+ PasswordStrengthIndicator.displayName = 'PasswordStrengthIndicator';
90
87
 
91
88
  const styles = StyleSheet.create({
92
89
  container: {
@@ -125,32 +125,6 @@ import { useUserProfile } from '@umituz/react-native-auth';
125
125
 
126
126
  **Documentation**: `useUserProfile.md`
127
127
 
128
- ---
129
-
130
-
131
- **Purpose**: Profile update operations and form management
132
-
133
- **When to Use**:
134
- - Profile editing screens
135
- - Settings screens
136
- - Form state management
137
- - Profile modifications
138
-
139
- **Import Path**:
140
- ```typescript
141
- import {
142
- useProfileEdit
143
- } from '@umituz/react-native-auth';
144
- ```
145
-
146
-
147
- **Rules**:
148
- - MUST validate before update
149
- - MUST handle loading state
150
- - MUST show errors to user
151
- - MUST not allow anonymous updates
152
-
153
-
154
128
  ---
155
129
 
156
130
  ### useAccountManagement
@@ -180,37 +154,6 @@ import { useAccountManagement } from '@umituz/react-native-auth';
180
154
 
181
155
  ---
182
156
 
183
- ### useSocialLogin
184
-
185
- **Purpose**: Google and Apple authentication
186
-
187
- **When to Use**:
188
- - Social authentication needed
189
- - Want Google sign-in
190
- - Want Apple sign-in (iOS)
191
- - Unified social auth interface
192
-
193
- **Import Path**:
194
- ```typescript
195
- import {
196
- useSocialLogin,
197
- useGoogleAuth,
198
- useAppleAuth
199
- } from '@umituz/react-native-auth';
200
- ```
201
-
202
- **File**: `useSocialLogin.ts`
203
-
204
- **Rules**:
205
- - MUST configure providers before use
206
- - MUST check provider availability
207
- - MUST handle loading states
208
- - MUST handle platform differences
209
-
210
- **Documentation**: `useSocialLogin.md`
211
-
212
- ---
213
-
214
157
  ### useAuthBottomSheet
215
158
 
216
159
  **Purpose**: Auth modal management
@@ -330,7 +330,6 @@ import { useAccountManagement } from '@umituz/react-native-auth';
330
330
 
331
331
  - **`useAuth`** (`src/presentation/hooks/useAuth.ts`) - Authentication state
332
332
  - **`useUserProfile`** (`src/presentation/hooks/useUserProfile.ts`) - Profile data
333
- - **`useProfileEdit`** (`src/presentation/hooks/useProfileEdit.ts`) - Profile editing form
334
333
 
335
334
  ## Related Components
336
335
 
@@ -98,7 +98,16 @@ export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
98
98
  } catch (err: unknown) {
99
99
  setLocalError(handleAuthError(err));
100
100
  }
101
- }, [fields, signIn, translations, handleAuthError, getErrorMessage, clearFieldErrorsState, setLocalError]);
101
+ }, [
102
+ fields.email,
103
+ fields.password,
104
+ signIn,
105
+ translations,
106
+ handleAuthError,
107
+ getErrorMessage,
108
+ clearFieldErrorsState,
109
+ setLocalError,
110
+ ]);
102
111
 
103
112
  const handleContinueAnonymously = useCallback(async () => {
104
113
  try {
@@ -39,7 +39,9 @@ export const usePasswordPromptNavigation = (
39
39
  })
40
40
  );
41
41
  } catch (error) {
42
- console.error('[usePasswordPromptNavigation] Navigation failed:', error);
42
+ if (__DEV__) {
43
+ console.error('[usePasswordPromptNavigation] Navigation failed:', error);
44
+ }
43
45
  setPasswordPromptCallback(null);
44
46
  resolve(null);
45
47
  }
@@ -74,7 +74,9 @@ export function AuthProvider({ children, ErrorFallback = DefaultErrorFallback }:
74
74
  try {
75
75
  unsubscribe();
76
76
  } catch (error) {
77
- console.error('[AuthProvider] Cleanup failed:', error instanceof Error ? error.message : String(error));
77
+ if (__DEV__) {
78
+ console.error('[AuthProvider] Cleanup failed:', error instanceof Error ? error.message : String(error));
79
+ }
78
80
  }
79
81
  }
80
82
  };
@@ -78,11 +78,15 @@ export const useAuthModalStore = createStore<AuthModalState, AuthModalActions>({
78
78
  // If it's a promise, catch rejections
79
79
  if (result && typeof result.then === 'function') {
80
80
  result.catch((error) => {
81
- console.error('[AuthModalStore] Pending callback error:', error);
81
+ if (__DEV__) {
82
+ console.error('[AuthModalStore] Pending callback error:', error);
83
+ }
82
84
  });
83
85
  }
84
86
  } catch (error) {
85
- console.error('[AuthModalStore] Pending callback error:', error);
87
+ if (__DEV__) {
88
+ console.error('[AuthModalStore] Pending callback error:', error);
89
+ }
86
90
  }
87
91
  set({ pendingCallback: null });
88
92
  }
@@ -3,7 +3,9 @@ let callback: ((value: string | null) => void) | null = null;
3
3
  export const setPasswordPromptCallback = (cb: ((value: string | null) => void) | null): void => {
4
4
  // Warn if overriding an existing callback (indicates potential bug)
5
5
  if (callback && cb) {
6
- console.warn('[passwordPromptCallback] Overriding existing callback - this may indicate a bug');
6
+ if (__DEV__) {
7
+ console.warn('[passwordPromptCallback] Overriding existing callback - this may indicate a bug');
8
+ }
7
9
  }
8
10
  callback = cb;
9
11
  };
@@ -1,83 +0,0 @@
1
- /**
2
- * Profile Edit Hook
3
- * Simple profile editing with form state management
4
- * Apps provide image picker and backend update logic
5
- */
6
-
7
- import { useState, useCallback } from "react";
8
- import { validateProfileForm } from "../utils/form/validation/formValidators";
9
-
10
- export interface ProfileEditFormState {
11
- displayName: string;
12
- email: string;
13
- photoURL: string | null;
14
- isModified: boolean;
15
- }
16
-
17
- export interface UseProfileEditReturn {
18
- formState: ProfileEditFormState;
19
- setDisplayName: (value: string) => void;
20
- setEmail: (value: string) => void;
21
- setPhotoURL: (value: string | null) => void;
22
- resetForm: (initial: Partial<ProfileEditFormState>) => void;
23
- validateForm: () => { isValid: boolean; errors: string[] };
24
- }
25
-
26
- export const useProfileEdit = (
27
- initialState: Partial<ProfileEditFormState> = {},
28
- ): UseProfileEditReturn => {
29
- const [formState, setFormState] = useState<ProfileEditFormState>({
30
- displayName: initialState.displayName || "",
31
- email: initialState.email || "",
32
- photoURL: initialState.photoURL || null,
33
- isModified: false,
34
- });
35
-
36
- const setDisplayName = useCallback((value: string) => {
37
- setFormState((prev) => ({ ...prev, displayName: value, isModified: true }));
38
- }, []);
39
-
40
- const setEmail = useCallback((value: string) => {
41
- setFormState((prev) => ({ ...prev, email: value, isModified: true }));
42
- }, []);
43
-
44
- const setPhotoURL = useCallback((value: string | null) => {
45
- setFormState((prev) => ({ ...prev, photoURL: value, isModified: true }));
46
- }, []);
47
-
48
- const resetForm = useCallback((initial: Partial<ProfileEditFormState>) => {
49
- setFormState({
50
- displayName: initial.displayName || "",
51
- email: initial.email || "",
52
- photoURL: initial.photoURL || null,
53
- isModified: false,
54
- });
55
- }, []);
56
-
57
- const validateForm = useCallback((): {
58
- isValid: boolean;
59
- errors: string[];
60
- } => {
61
- const result = validateProfileForm(
62
- {
63
- displayName: formState.displayName,
64
- email: formState.email,
65
- },
66
- key => key // Identity function for translation
67
- );
68
-
69
- return {
70
- isValid: result.isValid,
71
- errors: result.errors.map((e) => e.message),
72
- };
73
- }, [formState]);
74
-
75
- return {
76
- formState,
77
- setDisplayName,
78
- setEmail,
79
- setPhotoURL,
80
- resetForm,
81
- validateForm,
82
- };
83
- };
@@ -1,381 +0,0 @@
1
- # Social Login Hooks
2
-
3
- Hooks for Google and Apple social authentication.
4
-
5
- ---
6
-
7
- ## useSocialLogin
8
-
9
- General social login functionality wrapper.
10
-
11
- ### Strategy
12
-
13
- **Purpose**: Provides unified interface for Google and Apple social authentication by wrapping `@umituz/react-native-firebase`'s `useSocialAuth`.
14
-
15
- **When to Use**:
16
- - Need both Google and Apple auth
17
- - Want unified social auth interface
18
- - Prefer single hook for multiple providers
19
- - Don't need provider-specific features
20
-
21
- **Import Path**:
22
- ```typescript
23
- import { useSocialLogin } from '@umituz/react-native-auth';
24
- ```
25
-
26
- **Hook Location**: `src/presentation/hooks/useSocialLogin.ts`
27
-
28
- **Dependency**: `@umituz/react-native-firebase`
29
-
30
- ### Rules
31
-
32
- **MUST**:
33
- - Configure provider settings before use
34
- - Check provider availability before showing buttons
35
- - Handle loading states appropriately
36
- - Display errors to users
37
- - Check `googleConfigured` before using Google
38
- - Check `appleAvailable` before using Apple
39
-
40
- **MUST NOT**:
41
- - Show unavailable provider buttons
42
- - Assume provider is configured
43
- - Ignore loading states
44
- - Bypass error handling
45
-
46
- ### Constraints
47
-
48
- **CONFIGURATION PARAMETERS**:
49
- - `google?: GoogleAuthConfig` - Google client IDs
50
- - `apple?: { enabled: boolean }` - Apple enable flag
51
-
52
- **RETURN VALUES**:
53
- - `signInWithGoogle: () => Promise<SocialAuthResult>` - Google sign-in
54
- - `signInWithApple: () => Promise<SocialAuthResult>` - Apple sign-in
55
- - `googleLoading: boolean` - Google loading state
56
- - `appleLoading: boolean` - Apple loading state
57
- - `googleConfigured: boolean` - Google configuration status
58
- - `appleAvailable: boolean` - Apple availability status
59
-
60
- **PLATFORM LIMITATIONS**:
61
- - Google: All platforms
62
- - Apple: iOS only (returns `appleAvailable: false` on other platforms)
63
-
64
- ---
65
-
66
- ## useGoogleAuth
67
-
68
- Google OAuth authentication with expo-auth-session.
69
-
70
- ### Strategy
71
-
72
- **Purpose**: Complete Google OAuth flow using expo-auth-session for cross-platform support.
73
-
74
- **When to Use**:
75
- - Need Google OAuth specifically
76
- - Want full OAuth flow (not just Firebase)
77
- - Need web/expo support
78
- - Require custom Google configuration
79
-
80
- **Import Path**:
81
- ```typescript
82
- import { useGoogleAuth } from '@umituz/react-native-auth';
83
- ```
84
-
85
- **Hook Location**: `src/presentation/hooks/useSocialLogin.ts`
86
-
87
- **Dependencies**:
88
- - `expo-auth-session`
89
- - `expo-web-browser`
90
- - `@react-native-firebase/auth`
91
-
92
- ### Rules
93
-
94
- **MUST**:
95
- - Provide at least one client ID
96
- - Call `WebBrowser.maybeCompleteAuthSession()` in app root
97
- - Check `googleConfigured` before showing button
98
- - Handle loading and error states
99
- - Support platform-specific client IDs
100
-
101
- **MUST NOT**:
102
- - Call without client ID configuration
103
- - Skip web browser setup
104
- - Ignore platform differences
105
- - Show button if not configured
106
-
107
- ### Constraints
108
-
109
- **CLIENT IDs REQUIRED**:
110
- - `iosClientId?: string` - iOS client ID (optional)
111
- - `webClientId?: string` - Web client ID (optional)
112
- - `androidClientId?: string` - Android client ID (optional)
113
- - At least one MUST be provided
114
-
115
- **RETURN VALUES**:
116
- - `signInWithGoogle: () => Promise<SocialAuthResult>` - Sign-in function
117
- - `googleLoading: boolean` - Loading state
118
- - `googleConfigured: boolean` - Configuration status
119
-
120
- **PLATFORM BEHAVIOR**:
121
- - iOS: Uses `iosClientId` or falls back to `webClientId`
122
- - Android: Uses `androidClientId` or falls back to `webClientId`
123
- - Web: Uses `webClientId`
124
- - Requires OAuth 2.0 client ID from Google Cloud Console
125
-
126
- **SETUP REQUIREMENTS**:
127
- - OAuth 2.0 Client ID from Google Cloud Console
128
- - Authorized redirect URI (auto-configured by expo)
129
- - Web browser warm-up (maybeCompleteAuthSession)
130
-
131
- ---
132
-
133
- ## useAppleAuth
134
-
135
- Apple Sign-In authentication wrapper.
136
-
137
- ### Strategy
138
-
139
- **Purpose**: Convenience wrapper for Apple Sign-In functionality on iOS.
140
-
141
- **When to Use**:
142
- - Need Apple Sign-In specifically
143
- - Targeting iOS users
144
- - Want simple Apple auth integration
145
- - Don't need custom Apple configuration
146
-
147
- **Import Path**:
148
- ```typescript
149
- import { useAppleAuth } from '@umituz/react-native-auth';
150
- ```
151
-
152
- **Hook Location**: `src/presentation/hooks/useSocialLogin.ts`
153
-
154
- **Dependencies**:
155
- - `expo-apple-authentication`
156
- - `@react-native-firebase/auth`
157
-
158
- ### Rules
159
-
160
- **MUST**:
161
- - Check `appleAvailable` before showing button
162
- - Handle loading and error states
163
- - Only show on iOS platform
164
- - Support Apple Sign-In requirements
165
-
166
- **MUST NOT**:
167
- - Show Apple button on Android/Web
168
- - Call without availability check
169
- - Ignore Apple's guidelines
170
- - Require Apple auth for all users
171
-
172
- ### Constraints
173
-
174
- **RETURN VALUES**:
175
- - `signInWithApple: () => Promise<SocialAuthResult>` - Sign-in function
176
- - `appleLoading: boolean` - Loading state
177
- - `appleAvailable: boolean` - iOS availability status
178
-
179
- **PLATFORM SUPPORT**:
180
- - iOS: ✅ Fully supported (if configured)
181
- - Android: ❌ Not supported (appleAvailable = false)
182
- - Web: ❌ Not supported (appleAvailable = false)
183
-
184
- **SETUP REQUIREMENTS**:
185
- - Apple Developer account
186
- - App ID with Sign In with Apple enabled
187
- - Firebase Auth with Apple enabled
188
- - Physical device (may not work in simulator)
189
-
190
- **APPLE GUIDELINES**:
191
- - Must offer alternative auth methods
192
- - Cannot require Apple as only option
193
- - Must follow Apple's UI guidelines
194
- - Button design per Apple specifications
195
-
196
- ---
197
-
198
- ## SocialAuthResult
199
-
200
- Common return type for all social auth functions.
201
-
202
- ### Structure
203
-
204
- **PROPERTIES**:
205
- - `success: boolean` - Operation success status
206
- - `error?: string` - Error message if failed
207
- - `user?: AuthUser` - User object if successful
208
-
209
- **Rules**:
210
- - MUST check `success` before using `user`
211
- - MUST handle `error` if `success = false`
212
- - MUST NOT assume `user` exists without checking
213
-
214
- **Constraints**:
215
- - Always returns success boolean
216
- - User object only present on success
217
- - Error string only present on failure
218
- - Used by all social auth functions
219
-
220
- ---
221
-
222
- ## Configuration Strategy
223
-
224
- ### Strategy
225
-
226
- **Purpose**: Proper setup and configuration for social authentication.
227
-
228
- **Rules**:
229
- - MUST configure OAuth providers in Firebase Console
230
- - MUST set up projects in provider consoles
231
- - MUST provide correct client IDs
232
- - MUST test on physical devices
233
-
234
- **MUST NOT**:
235
- - Use development client IDs in production
236
- - Skip provider console setup
237
- - Assume configuration is correct
238
- - Test only on simulator
239
-
240
- ### Constraints
241
-
242
- **FIREBASE SETUP**:
243
- - Enable Google Sign-In in Firebase Auth
244
- - Enable Apple Sign-In in Firebase Auth
245
- - Configure OAuth consent screen
246
- - Set up authorized domains
247
-
248
- **GOOGLE CONSOLE SETUP**:
249
- 1. Go to Google Cloud Console
250
- 2. Create OAuth 2.0 Client IDs
251
- 3. Add authorized redirect URIs
252
- 4. Copy client IDs to app config
253
-
254
- **APPLE SETUP**:
255
- 1. Apple Developer account
256
- 2. Enable Sign In with Apple for App ID
257
- 3. Create provider ID in Firebase
258
- 4. Configure certificates
259
-
260
- ---
261
-
262
- ## Error Handling
263
-
264
- ### Strategy
265
-
266
- **Purpose**: Graceful handling of social authentication failures.
267
-
268
- **Rules**:
269
- - MUST distinguish cancellation from errors
270
- - MUST show user-friendly error messages
271
- - MUST allow retry after failures
272
- - MUST not crash on auth failures
273
-
274
- **MUST NOT**:
275
- - Show raw OAuth errors
276
- - Block retry indefinitely
277
- - Crash on provider errors
278
- - Expose sensitive tokens in errors
279
-
280
- ### Constraints
281
-
282
- **ERROR TYPES**:
283
- - User cancellation: Silent handling
284
- - Network errors: Retry prompt
285
- - Configuration errors: Developer message
286
- - Provider errors: Generic "try again"
287
-
288
- **CANCELLATION HANDLING**:
289
- - Check error message for "cancelled"
290
- - Don't show error for cancellation
291
- - Allow retry without blocking
292
- - Silent return preferred
293
-
294
- ---
295
-
296
- ## Security Requirements
297
-
298
- ### Strategy
299
-
300
- **Purpose**: Secure social authentication implementation.
301
-
302
- **Rules**:
303
- - MUST use HTTPS for all OAuth endpoints
304
- - MUST store tokens securely
305
- - MUST validate tokens server-side
306
- - MUST never log OAuth credentials
307
- - MUST implement token refresh
308
-
309
- **MUST NOT**:
310
- - Store tokens in AsyncStorage
311
- - Log OAuth responses
312
- - Skip server-side validation
313
- - Expose client secrets
314
- - Use HTTP for OAuth flows
315
-
316
- ### Constraints
317
-
318
- **TOKEN HANDLING**:
319
- - Tokens managed by Firebase SDK
320
- - Secure storage automatic
321
- - App never accesses refresh tokens
322
- - ID tokens available for API calls
323
- - Token refresh handled by Firebase
324
-
325
- **CLIENT SECRETS**:
326
- - Never included in app code
327
- - Public client flows only
328
- - Server-side validation required
329
- - Firebase manages credentials
330
-
331
- ---
332
-
333
- ## Platform-Specific Behavior
334
-
335
- ### Strategy
336
-
337
- **Purpose**: Optimize social auth experience for each platform.
338
-
339
- **Rules**:
340
- - MUST respect platform limitations
341
- - MUST use appropriate client IDs
342
- - MUST handle platform-specific errors
343
- - MUST test on target platforms
344
-
345
- **Constraints**:
346
-
347
- **iOS**:
348
- - Apple Sign-In available
349
- - Google uses app-based OAuth
350
- - Requires Info.plist configuration
351
- - Best on physical devices
352
-
353
- **Android**:
354
- - Apple Sign-In NOT available
355
- - Google uses app-based OAuth
356
- - Requires google-services.json
357
- - Works on emulator
358
-
359
- **Web**:
360
- - Apple Sign-In NOT available
361
- - Google uses popup OAuth
362
- - Requires proper callback handling
363
- - Browser popup blockers
364
-
365
- ---
366
-
367
- ## Related Hooks
368
-
369
- - **`useAuth`** (`src/presentation/hooks/useAuth.ts`) - Core authentication state
370
- - **`useAuthBottomSheet`** (`src/presentation/hooks/useAuthBottomSheet.md`) - Auth modal integration
371
-
372
- ## Related Components
373
-
374
- - **`SocialLoginButtons`** (`src/presentation/components/SocialLoginButtons.md`) - Social auth UI
375
-
376
- ## External Dependencies
377
-
378
- - **`@umituz/react-native-firebase`** - Firebase social auth wrapper
379
- - **`expo-auth-session`** - OAuth session management
380
- - **`expo-web-browser`** - Web browser for OAuth
381
- - **`expo-apple-authentication`** - Apple Sign-In
@@ -1,95 +0,0 @@
1
- /**
2
- * useSocialLogin Hook
3
- * Provides unified social login functionality using Firebase package
4
- *
5
- * This hook wraps @umituz/react-native-firebase's useSocialAuth
6
- * and provides a simple interface for social authentication.
7
- *
8
- * Usage:
9
- * ```typescript
10
- * const { signInWithGoogle, signInWithApple, googleLoading, appleLoading } = useSocialLogin({
11
- * google: { webClientId: '...', iosClientId: '...' },
12
- * apple: { enabled: true }
13
- * });
14
- * ```
15
- */
16
-
17
- import { useCallback } from "react";
18
- import { Platform } from "react-native";
19
- import {
20
- useSocialAuth,
21
- type SocialAuthConfig,
22
- type SocialAuthResult,
23
- } from "@umituz/react-native-firebase";
24
-
25
- export interface UseSocialLoginConfig extends SocialAuthConfig {}
26
-
27
- export interface UseSocialLoginResult {
28
- /** Sign in with Google (handles OAuth flow internally if configured) */
29
- signInWithGoogle: () => Promise<SocialAuthResult>;
30
- /** Sign in with Apple */
31
- signInWithApple: () => Promise<SocialAuthResult>;
32
- /** Whether Google sign-in is in progress */
33
- googleLoading: boolean;
34
- /** Whether Apple sign-in is in progress */
35
- appleLoading: boolean;
36
- /** Whether Google is configured */
37
- googleConfigured: boolean;
38
- /** Whether Apple is available */
39
- appleAvailable: boolean;
40
- }
41
-
42
- /**
43
- * Hook for social authentication
44
- * Integrates with @umituz/react-native-firebase for Firebase auth
45
- */
46
- export function useSocialLogin(config?: UseSocialLoginConfig): UseSocialLoginResult {
47
- const {
48
- signInWithApple: firebaseSignInWithApple,
49
- googleLoading,
50
- appleLoading,
51
- googleConfigured,
52
- appleAvailable,
53
- } = useSocialAuth(config);
54
-
55
- /**
56
- * Sign in with Google
57
- * Note: For full OAuth flow, use useGoogleAuth hook which handles
58
- * expo-auth-session OAuth flow and Firebase authentication
59
- */
60
- const signInWithGoogle = useCallback((): Promise<SocialAuthResult> => {
61
- if (!googleConfigured) {
62
- return Promise.resolve({ success: false, error: "Google Sign-In is not configured" });
63
- }
64
-
65
- return Promise.resolve({
66
- success: false,
67
- error: "Use useGoogleAuth hook for Google OAuth flow",
68
- });
69
- }, [googleConfigured]);
70
-
71
- /**
72
- * Sign in with Apple (full flow handled by Firebase package)
73
- */
74
- const signInWithApple = useCallback(async (): Promise<SocialAuthResult> => {
75
- if (Platform.OS !== "ios") {
76
- return { success: false, error: "Apple Sign-In is only available on iOS" };
77
- }
78
-
79
- if (!appleAvailable) {
80
- return { success: false, error: "Apple Sign-In is not available" };
81
- }
82
-
83
- return firebaseSignInWithApple();
84
- }, [appleAvailable, firebaseSignInWithApple]);
85
-
86
- return {
87
- signInWithGoogle,
88
- signInWithApple,
89
- googleLoading,
90
- appleLoading,
91
- googleConfigured,
92
- appleAvailable,
93
- };
94
- }
95
-