@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
|
@@ -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
|
|
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
|
|
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
|
-
|
|
55
|
-
|
|
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: {
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Social Login Buttons Component
|
|
3
|
+
* Google and Apple sign-in buttons
|
|
4
|
+
* PERFORMANCE: Memoized and provider checks memoized to prevent re-renders
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useMemo, 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, AtomicButton } from "@umituz/react-native-design-system/atoms";
|
|
@@ -19,17 +25,14 @@ interface SocialLoginButtonsProps {
|
|
|
19
25
|
appleLoading?: boolean;
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
export const SocialLoginButtons
|
|
23
|
-
translations,
|
|
24
|
-
enabledProviders,
|
|
25
|
-
onGooglePress,
|
|
26
|
-
onApplePress,
|
|
27
|
-
googleLoading = false,
|
|
28
|
-
appleLoading = false,
|
|
29
|
-
}) => {
|
|
28
|
+
export const SocialLoginButtons = memo<SocialLoginButtonsProps>(({ translations, enabledProviders, onGooglePress, onApplePress, googleLoading = false, appleLoading = false }) => {
|
|
30
29
|
const tokens = useAppDesignTokens();
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
|
|
31
|
+
// PERFORMANCE: Memoize provider checks to prevent recalculation on every render
|
|
32
|
+
const { hasGoogle, hasApple } = useMemo(() => ({
|
|
33
|
+
hasGoogle: enabledProviders.includes("google"),
|
|
34
|
+
hasApple: enabledProviders.includes("apple"),
|
|
35
|
+
}), [enabledProviders]);
|
|
33
36
|
|
|
34
37
|
if (!hasGoogle && !hasApple) {
|
|
35
38
|
return null;
|
|
@@ -74,7 +77,9 @@ export const SocialLoginButtons: React.FC<SocialLoginButtonsProps> = ({
|
|
|
74
77
|
</View>
|
|
75
78
|
</View>
|
|
76
79
|
);
|
|
77
|
-
};
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
SocialLoginButtons.displayName = 'SocialLoginButtons';
|
|
78
83
|
|
|
79
84
|
const styles = StyleSheet.create({
|
|
80
85
|
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
|
-
}, [
|
|
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
|
-
|
|
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
|
-
|
|
77
|
+
if (__DEV__) {
|
|
78
|
+
console.error('[AuthProvider] Cleanup failed:', error instanceof Error ? error.message : String(error));
|
|
79
|
+
}
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
82
|
};
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Account Screen
|
|
3
3
|
* Pure UI component for account management
|
|
4
4
|
* Business logic provided via props from app layer
|
|
5
|
+
* PERFORMANCE: Memoized to prevent unnecessary re-renders
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
import React from "react";
|
|
8
|
+
import React, { memo } from "react";
|
|
8
9
|
import { View, TouchableOpacity, StyleSheet } from "react-native";
|
|
9
10
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
10
11
|
import { AtomicIcon, AtomicText } from "@umituz/react-native-design-system/atoms";
|
|
@@ -29,7 +30,7 @@ export interface AccountScreenProps {
|
|
|
29
30
|
config: AccountScreenConfig;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
export const AccountScreen
|
|
33
|
+
export const AccountScreen = memo<AccountScreenProps>(({ config }) => {
|
|
33
34
|
const tokens = useAppDesignTokens();
|
|
34
35
|
|
|
35
36
|
return (
|
|
@@ -74,7 +75,9 @@ export const AccountScreen: React.FC<AccountScreenProps> = ({ config }) => {
|
|
|
74
75
|
{config.PasswordPromptComponent}
|
|
75
76
|
</>
|
|
76
77
|
);
|
|
77
|
-
};
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
AccountScreen.displayName = 'AccountScreen';
|
|
78
81
|
|
|
79
82
|
const styles = StyleSheet.create({
|
|
80
83
|
content: {
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Login Screen Component
|
|
3
|
+
* Login form screen with navigation
|
|
4
|
+
* PERFORMANCE: Memoized to prevent unnecessary re-renders
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { memo, useCallback } from "react";
|
|
2
8
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
3
9
|
import { AtomicCard } from "@umituz/react-native-design-system/atoms";
|
|
4
10
|
import { useAppNavigation } from "@umituz/react-native-design-system/molecules";
|
|
@@ -16,13 +22,14 @@ export interface LoginScreenProps {
|
|
|
16
22
|
translations: LoginScreenTranslations;
|
|
17
23
|
}
|
|
18
24
|
|
|
19
|
-
export const LoginScreen
|
|
25
|
+
export const LoginScreen = memo<LoginScreenProps>(({ translations }) => {
|
|
20
26
|
const navigation = useAppNavigation();
|
|
21
27
|
const tokens = useAppDesignTokens();
|
|
22
28
|
|
|
23
|
-
|
|
29
|
+
// PERFORMANCE: Stable callback reference
|
|
30
|
+
const handleNavigateToRegister = useCallback(() => {
|
|
24
31
|
navigation.navigate("Register");
|
|
25
|
-
};
|
|
32
|
+
}, [navigation]);
|
|
26
33
|
|
|
27
34
|
return (
|
|
28
35
|
<ScreenLayout
|
|
@@ -41,4 +48,6 @@ export const LoginScreen: React.FC<LoginScreenProps> = ({ translations }) => {
|
|
|
41
48
|
</AtomicCard>
|
|
42
49
|
</ScreenLayout>
|
|
43
50
|
);
|
|
44
|
-
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
LoginScreen.displayName = 'LoginScreen';
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Register Screen Component
|
|
3
|
+
* Registration form screen with navigation
|
|
4
|
+
* PERFORMANCE: Memoized to prevent unnecessary re-renders
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { memo, useCallback } from "react";
|
|
2
8
|
import { useAppDesignTokens } from "@umituz/react-native-design-system/theme";
|
|
3
9
|
import { AtomicCard } from "@umituz/react-native-design-system/atoms";
|
|
4
10
|
import { useAppNavigation } from "@umituz/react-native-design-system/molecules";
|
|
@@ -20,19 +26,14 @@ export interface RegisterScreenProps {
|
|
|
20
26
|
onPrivacyPress?: () => void;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
|
-
export const RegisterScreen
|
|
24
|
-
translations,
|
|
25
|
-
termsUrl,
|
|
26
|
-
privacyUrl,
|
|
27
|
-
onTermsPress,
|
|
28
|
-
onPrivacyPress,
|
|
29
|
-
}) => {
|
|
29
|
+
export const RegisterScreen = memo<RegisterScreenProps>(({ translations, termsUrl, privacyUrl, onTermsPress, onPrivacyPress }) => {
|
|
30
30
|
const navigation = useAppNavigation();
|
|
31
31
|
const tokens = useAppDesignTokens();
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
// PERFORMANCE: Stable callback reference
|
|
34
|
+
const handleNavigateToLogin = useCallback(() => {
|
|
34
35
|
navigation.navigate("Login");
|
|
35
|
-
};
|
|
36
|
+
}, [navigation]);
|
|
36
37
|
|
|
37
38
|
return (
|
|
38
39
|
<ScreenLayout
|
|
@@ -55,4 +56,6 @@ export const RegisterScreen: React.FC<RegisterScreenProps> = ({
|
|
|
55
56
|
</AtomicCard>
|
|
56
57
|
</ScreenLayout>
|
|
57
58
|
);
|
|
58
|
-
};
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
RegisterScreen.displayName = 'RegisterScreen';
|
|
@@ -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
|
-
|
|
81
|
+
if (__DEV__) {
|
|
82
|
+
console.error('[AuthModalStore] Pending callback error:', error);
|
|
83
|
+
}
|
|
82
84
|
});
|
|
83
85
|
}
|
|
84
86
|
} catch (error) {
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
};
|