@umituz/react-native-auth 3.6.76 → 3.6.78
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 +5 -5
- package/src/infrastructure/providers/FirebaseAuthProvider.ts +11 -3
- package/src/infrastructure/utils/AuthErrorMapper.ts +18 -74
- package/src/infrastructure/utils/error/errorCodeMapping.constants.ts +120 -0
- package/src/infrastructure/utils/listener/listenerLifecycle.util.ts +42 -27
- package/src/presentation/components/LoginForm.tsx +10 -25
- package/src/presentation/components/RegisterForm.tsx +23 -43
- package/src/presentation/components/form/FormEmailInput.tsx +60 -0
- package/src/presentation/components/form/FormPasswordInput.tsx +62 -0
- package/src/presentation/components/form/FormTextInput.tsx +60 -0
- package/src/presentation/components/form/index.ts +6 -0
- package/src/presentation/hooks/useAccountManagement.ts +32 -2
- package/src/presentation/hooks/useAuth.ts +9 -3
- package/src/presentation/hooks/useAuthBottomSheet.ts +8 -6
- package/src/presentation/hooks/useLoginForm.ts +30 -17
- package/src/presentation/hooks/useRegisterForm.ts +74 -59
- package/src/presentation/stores/authStore.ts +15 -6
- package/src/presentation/stores/initializeAuthListener.ts +9 -3
- package/src/presentation/utils/authTransition.util.ts +13 -2
- package/src/presentation/utils/form/useFormField.hook.ts +97 -0
- package/src/presentation/utils/form/usePasswordValidation.hook.ts +87 -0
- package/src/presentation/utils/getAuthErrorMessage.ts +17 -40
- package/src/presentation/utils/socialAuthHandler.util.ts +20 -37
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-auth",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.78",
|
|
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",
|
|
@@ -60,9 +60,9 @@
|
|
|
60
60
|
"@types/react": "~19.1.0",
|
|
61
61
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
62
62
|
"@typescript-eslint/parser": "^7.0.0",
|
|
63
|
-
"@umituz/react-native-design-system": "
|
|
64
|
-
"@umituz/react-native-firebase": "
|
|
65
|
-
"@umituz/react-native-localization": "
|
|
63
|
+
"@umituz/react-native-design-system": "latest",
|
|
64
|
+
"@umituz/react-native-firebase": "latest",
|
|
65
|
+
"@umituz/react-native-localization": "latest",
|
|
66
66
|
"eslint": "^8.57.0",
|
|
67
67
|
"expo-apple-authentication": "^6.0.0",
|
|
68
68
|
"expo-application": "^7.0.8",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"react-native-safe-area-context": "^5.6.2",
|
|
94
94
|
"react-native-svg": "^15.15.1",
|
|
95
95
|
"typescript": "^5.3.0",
|
|
96
|
-
"zustand": "^
|
|
96
|
+
"zustand": "^5.0.0",
|
|
97
97
|
"@react-native-community/datetimepicker": "^8.2.0",
|
|
98
98
|
"rn-emoji-keyboard": "^1.7.0"
|
|
99
99
|
},
|
|
@@ -60,7 +60,13 @@ export class FirebaseAuthProvider implements IAuthProvider {
|
|
|
60
60
|
let userCredential;
|
|
61
61
|
|
|
62
62
|
if (currentUser && isAnonymous) {
|
|
63
|
-
|
|
63
|
+
// Try to reload to get fresh user data, but don't fail if it doesn't work
|
|
64
|
+
try {
|
|
65
|
+
await currentUser.reload();
|
|
66
|
+
} catch (reloadError) {
|
|
67
|
+
// Log the reload error but proceed - the user might still be valid
|
|
68
|
+
console.warn("[FirebaseAuthProvider] User reload failed during anonymous upgrade:", reloadError);
|
|
69
|
+
}
|
|
64
70
|
const credential = EmailAuthProvider.credential(credentials.email.trim(), credentials.password);
|
|
65
71
|
userCredential = await linkWithCredential(currentUser, credential);
|
|
66
72
|
} else {
|
|
@@ -70,8 +76,10 @@ export class FirebaseAuthProvider implements IAuthProvider {
|
|
|
70
76
|
if (credentials.displayName && userCredential.user) {
|
|
71
77
|
try {
|
|
72
78
|
await updateProfile(userCredential.user, { displayName: credentials.displayName.trim() });
|
|
73
|
-
} catch {
|
|
74
|
-
|
|
79
|
+
} catch (profileError) {
|
|
80
|
+
// Display name update failed, but account was created successfully
|
|
81
|
+
// Log this for debugging but don't fail the entire operation
|
|
82
|
+
console.warn("[FirebaseAuthProvider] Display name update failed:", profileError);
|
|
75
83
|
}
|
|
76
84
|
}
|
|
77
85
|
|
|
@@ -10,17 +10,24 @@
|
|
|
10
10
|
import {
|
|
11
11
|
AuthError,
|
|
12
12
|
AuthConfigurationError,
|
|
13
|
-
AuthEmailAlreadyInUseError,
|
|
14
|
-
AuthInvalidEmailError,
|
|
15
|
-
AuthWeakPasswordError,
|
|
16
|
-
AuthUserNotFoundError,
|
|
17
|
-
AuthWrongPasswordError,
|
|
18
13
|
AuthNetworkError,
|
|
19
14
|
} from "../../domain/errors/AuthError";
|
|
20
15
|
import {
|
|
21
16
|
extractErrorCode,
|
|
22
17
|
extractErrorMessage,
|
|
23
18
|
} from "./error/errorExtraction";
|
|
19
|
+
import { ERROR_CODE_MAP, type ErrorConstructor, type ErrorFactory } from "./error/errorCodeMapping.constants";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create error from error mapping
|
|
23
|
+
* @param mapping - Error mapping configuration
|
|
24
|
+
* @returns Created error instance
|
|
25
|
+
*/
|
|
26
|
+
function createErrorFromMapping(mapping: { type: "class" | "factory"; create: ErrorConstructor | ErrorFactory }): Error {
|
|
27
|
+
return mapping.type === "class"
|
|
28
|
+
? new (mapping.create as ErrorConstructor)()
|
|
29
|
+
: (mapping.create as ErrorFactory)();
|
|
30
|
+
}
|
|
24
31
|
|
|
25
32
|
/**
|
|
26
33
|
* Map Firebase Auth errors to domain errors
|
|
@@ -54,76 +61,13 @@ export function mapFirebaseAuthError(error: unknown): Error {
|
|
|
54
61
|
const message = extractErrorMessage(error);
|
|
55
62
|
|
|
56
63
|
// Map known Firebase Auth error codes to domain errors
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
case "auth/invalid-email":
|
|
62
|
-
return new AuthInvalidEmailError();
|
|
63
|
-
|
|
64
|
-
case "auth/weak-password":
|
|
65
|
-
return new AuthWeakPasswordError();
|
|
66
|
-
|
|
67
|
-
case "auth/user-disabled":
|
|
68
|
-
return new AuthError(
|
|
69
|
-
"Your account has been disabled. Please contact support.",
|
|
70
|
-
"AUTH_USER_DISABLED"
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
case "auth/user-not-found":
|
|
74
|
-
return new AuthUserNotFoundError();
|
|
75
|
-
|
|
76
|
-
case "auth/wrong-password":
|
|
77
|
-
return new AuthWrongPasswordError();
|
|
78
|
-
|
|
79
|
-
case "auth/invalid-credential":
|
|
80
|
-
case "auth/invalid-login-credentials":
|
|
81
|
-
return new AuthError(
|
|
82
|
-
"Invalid email or password. Please check your credentials.",
|
|
83
|
-
"AUTH_INVALID_CREDENTIAL"
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
case "auth/network-request-failed":
|
|
87
|
-
return new AuthNetworkError();
|
|
88
|
-
|
|
89
|
-
case "auth/too-many-requests":
|
|
90
|
-
return new AuthError(
|
|
91
|
-
"Too many failed attempts. Please wait a few minutes and try again.",
|
|
92
|
-
"AUTH_TOO_MANY_REQUESTS"
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
case "auth/configuration-not-found":
|
|
96
|
-
case "auth/app-not-authorized":
|
|
97
|
-
return new AuthConfigurationError(
|
|
98
|
-
"Authentication is not properly configured. Please contact support."
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
case "auth/operation-not-allowed":
|
|
102
|
-
return new AuthConfigurationError(
|
|
103
|
-
"Email/password authentication is not enabled. Please contact support."
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
case "auth/requires-recent-login":
|
|
107
|
-
return new AuthError(
|
|
108
|
-
"Please sign in again to complete this action.",
|
|
109
|
-
"AUTH_REQUIRES_RECENT_LOGIN"
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
case "auth/expired-action-code":
|
|
113
|
-
return new AuthError(
|
|
114
|
-
"This link has expired. Please request a new one.",
|
|
115
|
-
"AUTH_EXPIRED_ACTION_CODE"
|
|
116
|
-
);
|
|
117
|
-
|
|
118
|
-
case "auth/invalid-action-code":
|
|
119
|
-
return new AuthError(
|
|
120
|
-
"This link is invalid. Please request a new one.",
|
|
121
|
-
"AUTH_INVALID_ACTION_CODE"
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
default:
|
|
125
|
-
return new AuthError(message, code || "AUTH_UNKNOWN_ERROR");
|
|
64
|
+
const mapping = ERROR_CODE_MAP[code];
|
|
65
|
+
if (mapping) {
|
|
66
|
+
return createErrorFromMapping(mapping);
|
|
126
67
|
}
|
|
68
|
+
|
|
69
|
+
// Default fallback for unknown error codes
|
|
70
|
+
return new AuthError(message, code || "AUTH_UNKNOWN_ERROR");
|
|
127
71
|
}
|
|
128
72
|
|
|
129
73
|
/**
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Error Code Mapping Constants
|
|
3
|
+
* Centralized configuration for mapping Firebase error codes to domain errors
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
AuthError,
|
|
8
|
+
AuthConfigurationError,
|
|
9
|
+
AuthEmailAlreadyInUseError,
|
|
10
|
+
AuthInvalidEmailError,
|
|
11
|
+
AuthWeakPasswordError,
|
|
12
|
+
AuthUserNotFoundError,
|
|
13
|
+
AuthWrongPasswordError,
|
|
14
|
+
AuthNetworkError,
|
|
15
|
+
} from "../../../domain/errors/AuthError";
|
|
16
|
+
|
|
17
|
+
export type ErrorConstructor = new (message?: string) => Error;
|
|
18
|
+
export type ErrorFactory = () => Error;
|
|
19
|
+
|
|
20
|
+
export interface ErrorMapping {
|
|
21
|
+
type: "class" | "factory";
|
|
22
|
+
create: ErrorConstructor | ErrorFactory;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Firebase error code to domain error mapping
|
|
27
|
+
*/
|
|
28
|
+
export const ERROR_CODE_MAP: Record<string, ErrorMapping> = {
|
|
29
|
+
"auth/email-already-in-use": {
|
|
30
|
+
type: "class",
|
|
31
|
+
create: AuthEmailAlreadyInUseError,
|
|
32
|
+
},
|
|
33
|
+
"auth/invalid-email": {
|
|
34
|
+
type: "class",
|
|
35
|
+
create: AuthInvalidEmailError,
|
|
36
|
+
},
|
|
37
|
+
"auth/weak-password": {
|
|
38
|
+
type: "class",
|
|
39
|
+
create: AuthWeakPasswordError,
|
|
40
|
+
},
|
|
41
|
+
"auth/user-disabled": {
|
|
42
|
+
type: "factory",
|
|
43
|
+
create: () => new AuthError(
|
|
44
|
+
"Your account has been disabled. Please contact support.",
|
|
45
|
+
"AUTH_USER_DISABLED"
|
|
46
|
+
),
|
|
47
|
+
},
|
|
48
|
+
"auth/user-not-found": {
|
|
49
|
+
type: "class",
|
|
50
|
+
create: AuthUserNotFoundError,
|
|
51
|
+
},
|
|
52
|
+
"auth/wrong-password": {
|
|
53
|
+
type: "class",
|
|
54
|
+
create: AuthWrongPasswordError,
|
|
55
|
+
},
|
|
56
|
+
"auth/invalid-credential": {
|
|
57
|
+
type: "factory",
|
|
58
|
+
create: () => new AuthError(
|
|
59
|
+
"Invalid email or password. Please check your credentials.",
|
|
60
|
+
"AUTH_INVALID_CREDENTIAL"
|
|
61
|
+
),
|
|
62
|
+
},
|
|
63
|
+
"auth/invalid-login-credentials": {
|
|
64
|
+
type: "factory",
|
|
65
|
+
create: () => new AuthError(
|
|
66
|
+
"Invalid email or password. Please check your credentials.",
|
|
67
|
+
"AUTH_INVALID_CREDENTIAL"
|
|
68
|
+
),
|
|
69
|
+
},
|
|
70
|
+
"auth/network-request-failed": {
|
|
71
|
+
type: "class",
|
|
72
|
+
create: AuthNetworkError,
|
|
73
|
+
},
|
|
74
|
+
"auth/too-many-requests": {
|
|
75
|
+
type: "factory",
|
|
76
|
+
create: () => new AuthError(
|
|
77
|
+
"Too many failed attempts. Please wait a few minutes and try again.",
|
|
78
|
+
"AUTH_TOO_MANY_REQUESTS"
|
|
79
|
+
),
|
|
80
|
+
},
|
|
81
|
+
"auth/configuration-not-found": {
|
|
82
|
+
type: "factory",
|
|
83
|
+
create: () => new AuthConfigurationError(
|
|
84
|
+
"Authentication is not properly configured. Please contact support."
|
|
85
|
+
),
|
|
86
|
+
},
|
|
87
|
+
"auth/app-not-authorized": {
|
|
88
|
+
type: "factory",
|
|
89
|
+
create: () => new AuthConfigurationError(
|
|
90
|
+
"Authentication is not properly configured. Please contact support."
|
|
91
|
+
),
|
|
92
|
+
},
|
|
93
|
+
"auth/operation-not-allowed": {
|
|
94
|
+
type: "factory",
|
|
95
|
+
create: () => new AuthConfigurationError(
|
|
96
|
+
"Email/password authentication is not enabled. Please contact support."
|
|
97
|
+
),
|
|
98
|
+
},
|
|
99
|
+
"auth/requires-recent-login": {
|
|
100
|
+
type: "factory",
|
|
101
|
+
create: () => new AuthError(
|
|
102
|
+
"Please sign in again to complete this action.",
|
|
103
|
+
"AUTH_REQUIRES_RECENT_LOGIN"
|
|
104
|
+
),
|
|
105
|
+
},
|
|
106
|
+
"auth/expired-action-code": {
|
|
107
|
+
type: "factory",
|
|
108
|
+
create: () => new AuthError(
|
|
109
|
+
"This link has expired. Please request a new one.",
|
|
110
|
+
"AUTH_EXPIRED_ACTION_CODE"
|
|
111
|
+
),
|
|
112
|
+
},
|
|
113
|
+
"auth/invalid-action-code": {
|
|
114
|
+
type: "factory",
|
|
115
|
+
create: () => new AuthError(
|
|
116
|
+
"This link is invalid. Please request a new one.",
|
|
117
|
+
"AUTH_INVALID_ACTION_CODE"
|
|
118
|
+
),
|
|
119
|
+
},
|
|
120
|
+
};
|
|
@@ -74,11 +74,21 @@ export function setupAuthListener(
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
try {
|
|
78
|
+
const unsubscribe = onIdTokenChanged(auth, (user) => {
|
|
79
|
+
handleAuthStateChange(user, store, auth, autoAnonymousSignIn, onAuthStateChange);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
setUnsubscribe(unsubscribe);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// If listener setup fails, ensure we clean up and mark as initialized
|
|
85
|
+
console.error("[AuthListener] Failed to setup auth listener:", error);
|
|
86
|
+
completeInitialization();
|
|
87
|
+
store.setLoading(false);
|
|
88
|
+
store.setInitialized(true);
|
|
89
|
+
store.setError("Failed to initialize authentication listener");
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
/**
|
|
@@ -91,42 +101,47 @@ function handleAuthStateChange(
|
|
|
91
101
|
autoAnonymousSignIn: boolean,
|
|
92
102
|
onAuthStateChange?: (user: User | null) => void
|
|
93
103
|
): void {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
try {
|
|
105
|
+
if (!user && autoAnonymousSignIn) {
|
|
106
|
+
// Start anonymous sign-in without blocking
|
|
107
|
+
void handleAnonymousMode(store, auth);
|
|
108
|
+
store.setFirebaseUser(null);
|
|
109
|
+
completeInitialization();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
100
112
|
|
|
101
|
-
|
|
102
|
-
|
|
113
|
+
store.setFirebaseUser(user);
|
|
114
|
+
store.setInitialized(true);
|
|
103
115
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
116
|
+
// Handle conversion from anonymous
|
|
117
|
+
if (user && !user.isAnonymous && store.isAnonymous) {
|
|
118
|
+
store.setIsAnonymous(false);
|
|
119
|
+
}
|
|
108
120
|
|
|
109
|
-
|
|
121
|
+
onAuthStateChange?.(user);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error("[AuthListener] Error handling auth state change:", error);
|
|
124
|
+
// Ensure we don't leave the app in a bad state
|
|
125
|
+
store.setInitialized(true);
|
|
126
|
+
store.setLoading(false);
|
|
127
|
+
}
|
|
110
128
|
}
|
|
111
129
|
|
|
112
130
|
/**
|
|
113
131
|
* Handle anonymous mode sign-in
|
|
114
132
|
*/
|
|
115
|
-
function handleAnonymousMode(store: Store, auth: Auth): void {
|
|
133
|
+
async function handleAnonymousMode(store: Store, auth: Auth): Promise<void> {
|
|
116
134
|
if (!startAnonymousSignIn()) {
|
|
117
135
|
return; // Already signing in
|
|
118
136
|
}
|
|
119
137
|
|
|
120
138
|
const handleAnonymousSignIn = createAnonymousSignInHandler(auth, store);
|
|
121
139
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
completeAnonymousSignIn();
|
|
128
|
-
}
|
|
129
|
-
})();
|
|
140
|
+
try {
|
|
141
|
+
await handleAnonymousSignIn();
|
|
142
|
+
} finally {
|
|
143
|
+
completeAnonymousSignIn();
|
|
144
|
+
}
|
|
130
145
|
}
|
|
131
146
|
|
|
132
147
|
/**
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React, { useRef } from "react";
|
|
2
2
|
import { StyleSheet, TextInput } from "react-native";
|
|
3
|
-
import {
|
|
3
|
+
import { AtomicButton } from "@umituz/react-native-design-system";
|
|
4
4
|
import { useLoginForm } from "../hooks/useLoginForm";
|
|
5
5
|
import { AuthErrorDisplay } from "./AuthErrorDisplay";
|
|
6
6
|
import { AuthLink } from "./AuthLink";
|
|
7
|
+
import { FormEmailInput, FormPasswordInput } from "./form";
|
|
7
8
|
|
|
8
9
|
export interface LoginFormTranslations {
|
|
9
10
|
email: string;
|
|
@@ -39,40 +40,27 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
|
|
39
40
|
|
|
40
41
|
return (
|
|
41
42
|
<>
|
|
42
|
-
<
|
|
43
|
-
label={translations.email}
|
|
43
|
+
<FormEmailInput
|
|
44
44
|
value={email}
|
|
45
45
|
onChangeText={handleEmailChange}
|
|
46
|
+
label={translations.email}
|
|
46
47
|
placeholder={translations.emailPlaceholder}
|
|
47
|
-
|
|
48
|
-
autoCapitalize="none"
|
|
48
|
+
error={emailError}
|
|
49
49
|
disabled={loading}
|
|
50
|
-
state={emailError ? "error" : "default"}
|
|
51
|
-
helperText={emailError || undefined}
|
|
52
|
-
returnKeyType="next"
|
|
53
50
|
onSubmitEditing={() => passwordRef.current?.focus()}
|
|
54
|
-
|
|
55
|
-
textContentType="emailAddress"
|
|
56
|
-
style={styles.input}
|
|
51
|
+
returnKeyType="next"
|
|
57
52
|
/>
|
|
58
53
|
|
|
59
|
-
<
|
|
54
|
+
<FormPasswordInput
|
|
60
55
|
ref={passwordRef}
|
|
61
|
-
label={translations.password}
|
|
62
56
|
value={password}
|
|
63
57
|
onChangeText={handlePasswordChange}
|
|
58
|
+
label={translations.password}
|
|
64
59
|
placeholder={translations.passwordPlaceholder}
|
|
65
|
-
|
|
66
|
-
showPasswordToggle
|
|
67
|
-
autoCapitalize="none"
|
|
68
|
-
autoCorrect={false}
|
|
60
|
+
error={passwordError}
|
|
69
61
|
disabled={loading}
|
|
70
|
-
state={passwordError ? "error" : "default"}
|
|
71
|
-
helperText={passwordError || undefined}
|
|
72
|
-
returnKeyType="done"
|
|
73
62
|
onSubmitEditing={() => { void handleSignIn(); }}
|
|
74
|
-
|
|
75
|
-
style={styles.input}
|
|
63
|
+
returnKeyType="done"
|
|
76
64
|
/>
|
|
77
65
|
|
|
78
66
|
<AuthErrorDisplay error={displayError} />
|
|
@@ -98,9 +86,6 @@ export const LoginForm: React.FC<LoginFormProps> = ({
|
|
|
98
86
|
};
|
|
99
87
|
|
|
100
88
|
const styles = StyleSheet.create({
|
|
101
|
-
input: {
|
|
102
|
-
marginBottom: 20,
|
|
103
|
-
},
|
|
104
89
|
signInButton: {
|
|
105
90
|
minHeight: 52,
|
|
106
91
|
marginBottom: 16,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import React, { useRef } from "react";
|
|
2
2
|
import { StyleSheet, TextInput } from "react-native";
|
|
3
|
-
import {
|
|
3
|
+
import { AtomicButton } from "@umituz/react-native-design-system";
|
|
4
4
|
import { useRegisterForm } from "../hooks/useRegisterForm";
|
|
5
5
|
import { AuthErrorDisplay } from "./AuthErrorDisplay";
|
|
6
6
|
import { AuthLink } from "./AuthLink";
|
|
7
7
|
import { AuthLegalLinks, type AuthLegalLinksTranslations } from "./AuthLegalLinks";
|
|
8
8
|
import { PasswordStrengthIndicator, type PasswordStrengthTranslations } from "./PasswordStrengthIndicator";
|
|
9
9
|
import { PasswordMatchIndicator, type PasswordMatchTranslations } from "./PasswordMatchIndicator";
|
|
10
|
+
import { FormTextInput, FormEmailInput, FormPasswordInput } from "./form";
|
|
10
11
|
|
|
11
12
|
export interface RegisterFormTranslations {
|
|
12
13
|
displayName: string;
|
|
@@ -66,79 +67,57 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
|
|
66
67
|
|
|
67
68
|
return (
|
|
68
69
|
<>
|
|
69
|
-
<
|
|
70
|
-
label={translations.displayName}
|
|
70
|
+
<FormTextInput
|
|
71
71
|
value={displayName}
|
|
72
72
|
onChangeText={handleDisplayNameChange}
|
|
73
|
+
label={translations.displayName}
|
|
73
74
|
placeholder={translations.displayNamePlaceholder}
|
|
74
|
-
|
|
75
|
+
error={fieldErrors.displayName}
|
|
75
76
|
disabled={loading}
|
|
76
|
-
|
|
77
|
-
helperText={fieldErrors.displayName || undefined}
|
|
78
|
-
returnKeyType="next"
|
|
77
|
+
autoCapitalize="words"
|
|
79
78
|
onSubmitEditing={() => emailRef.current?.focus()}
|
|
80
|
-
|
|
81
|
-
style={styles.input}
|
|
79
|
+
returnKeyType="next"
|
|
82
80
|
/>
|
|
83
81
|
|
|
84
|
-
<
|
|
82
|
+
<FormEmailInput
|
|
85
83
|
ref={emailRef}
|
|
86
|
-
label={translations.email}
|
|
87
84
|
value={email}
|
|
88
85
|
onChangeText={handleEmailChange}
|
|
86
|
+
label={translations.email}
|
|
89
87
|
placeholder={translations.emailPlaceholder}
|
|
90
|
-
|
|
91
|
-
autoCapitalize="none"
|
|
88
|
+
error={fieldErrors.email}
|
|
92
89
|
disabled={loading}
|
|
93
|
-
state={fieldErrors.email ? "error" : "default"}
|
|
94
|
-
helperText={fieldErrors.email || undefined}
|
|
95
|
-
returnKeyType="next"
|
|
96
90
|
onSubmitEditing={() => passwordRef.current?.focus()}
|
|
97
|
-
|
|
98
|
-
textContentType="emailAddress"
|
|
99
|
-
style={styles.input}
|
|
91
|
+
returnKeyType="next"
|
|
100
92
|
/>
|
|
101
93
|
|
|
102
|
-
<
|
|
94
|
+
<FormPasswordInput
|
|
103
95
|
ref={passwordRef}
|
|
104
|
-
label={translations.password}
|
|
105
96
|
value={password}
|
|
106
97
|
onChangeText={handlePasswordChange}
|
|
98
|
+
label={translations.password}
|
|
107
99
|
placeholder={translations.passwordPlaceholder}
|
|
108
|
-
|
|
109
|
-
showPasswordToggle
|
|
110
|
-
autoCapitalize="none"
|
|
111
|
-
autoCorrect={false}
|
|
100
|
+
error={fieldErrors.password}
|
|
112
101
|
disabled={loading}
|
|
113
|
-
state={fieldErrors.password ? "error" : "default"}
|
|
114
|
-
helperText={fieldErrors.password || undefined}
|
|
115
|
-
returnKeyType="next"
|
|
116
102
|
onSubmitEditing={() => confirmPasswordRef.current?.focus()}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
style={styles.input}
|
|
103
|
+
returnKeyType="next"
|
|
104
|
+
style={styles.passwordInput}
|
|
120
105
|
/>
|
|
121
106
|
{password.length > 0 && (
|
|
122
107
|
<PasswordStrengthIndicator translations={translations.passwordStrength} requirements={passwordRequirements} />
|
|
123
108
|
)}
|
|
124
109
|
|
|
125
|
-
<
|
|
110
|
+
<FormPasswordInput
|
|
126
111
|
ref={confirmPasswordRef}
|
|
127
|
-
label={translations.confirmPassword}
|
|
128
112
|
value={confirmPassword}
|
|
129
113
|
onChangeText={handleConfirmPasswordChange}
|
|
114
|
+
label={translations.confirmPassword}
|
|
130
115
|
placeholder={translations.confirmPasswordPlaceholder}
|
|
131
|
-
|
|
132
|
-
showPasswordToggle
|
|
133
|
-
autoCapitalize="none"
|
|
134
|
-
autoCorrect={false}
|
|
116
|
+
error={fieldErrors.confirmPassword}
|
|
135
117
|
disabled={loading}
|
|
136
|
-
state={fieldErrors.confirmPassword ? "error" : "default"}
|
|
137
|
-
helperText={fieldErrors.confirmPassword || undefined}
|
|
138
|
-
returnKeyType="done"
|
|
139
118
|
onSubmitEditing={() => { void handleSignUp(); }}
|
|
140
|
-
|
|
141
|
-
style={styles.
|
|
119
|
+
returnKeyType="done"
|
|
120
|
+
style={styles.confirmPasswordInput}
|
|
142
121
|
/>
|
|
143
122
|
{confirmPassword.length > 0 && (
|
|
144
123
|
<PasswordMatchIndicator translations={translations.passwordMatch} isMatch={passwordsMatch} />
|
|
@@ -171,6 +150,7 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
|
|
171
150
|
};
|
|
172
151
|
|
|
173
152
|
const styles = StyleSheet.create({
|
|
174
|
-
|
|
153
|
+
passwordInput: { marginBottom: 4 },
|
|
154
|
+
confirmPasswordInput: { marginBottom: 4 },
|
|
175
155
|
signUpButton: { minHeight: 52, marginBottom: 16, marginTop: 8 },
|
|
176
156
|
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React, { forwardRef } from "react";
|
|
2
|
+
import { TextInput, StyleSheet, ViewStyle } from "react-native";
|
|
3
|
+
import { AtomicInput } from "@umituz/react-native-design-system";
|
|
4
|
+
|
|
5
|
+
export interface FormEmailInputProps {
|
|
6
|
+
value: string;
|
|
7
|
+
onChangeText: (text: string) => void;
|
|
8
|
+
label: string;
|
|
9
|
+
placeholder: string;
|
|
10
|
+
error?: string | null;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
onSubmitEditing?: () => void;
|
|
13
|
+
returnKeyType?: "next" | "done";
|
|
14
|
+
style?: ViewStyle;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const FormEmailInput = forwardRef<React.ElementRef<typeof TextInput>, FormEmailInputProps>(
|
|
18
|
+
(
|
|
19
|
+
{
|
|
20
|
+
value,
|
|
21
|
+
onChangeText,
|
|
22
|
+
label,
|
|
23
|
+
placeholder,
|
|
24
|
+
error,
|
|
25
|
+
disabled = false,
|
|
26
|
+
onSubmitEditing,
|
|
27
|
+
returnKeyType = "next",
|
|
28
|
+
style,
|
|
29
|
+
},
|
|
30
|
+
ref
|
|
31
|
+
) => {
|
|
32
|
+
return (
|
|
33
|
+
<AtomicInput
|
|
34
|
+
ref={ref}
|
|
35
|
+
label={label}
|
|
36
|
+
value={value}
|
|
37
|
+
onChangeText={onChangeText}
|
|
38
|
+
placeholder={placeholder}
|
|
39
|
+
keyboardType="email-address"
|
|
40
|
+
autoCapitalize="none"
|
|
41
|
+
disabled={disabled}
|
|
42
|
+
state={error ? "error" : "default"}
|
|
43
|
+
helperText={error || undefined}
|
|
44
|
+
returnKeyType={returnKeyType}
|
|
45
|
+
onSubmitEditing={onSubmitEditing}
|
|
46
|
+
blurOnSubmit={returnKeyType === "done"}
|
|
47
|
+
textContentType="emailAddress"
|
|
48
|
+
style={[styles.input, style]}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
FormEmailInput.displayName = "FormEmailInput";
|
|
55
|
+
|
|
56
|
+
const styles = StyleSheet.create({
|
|
57
|
+
input: {
|
|
58
|
+
marginBottom: 20,
|
|
59
|
+
},
|
|
60
|
+
});
|