@umituz/react-native-auth 3.5.6 → 3.5.8
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 +2 -2
- package/src/index.ts +0 -2
- package/src/presentation/components/AuthErrorDisplay.tsx +14 -37
- package/src/presentation/components/AuthHeader.tsx +3 -2
- package/src/presentation/components/AuthLegalLinks.tsx +6 -10
- package/src/presentation/components/LoginForm.tsx +44 -50
- package/src/presentation/components/RegisterForm.tsx +93 -101
- package/src/presentation/components/SocialLoginButtons.tsx +24 -76
- package/src/presentation/screens/LoginScreen.tsx +12 -7
- package/src/presentation/screens/RegisterScreen.tsx +12 -7
- package/src/presentation/components/AuthBackground.tsx +0 -21
- package/src/presentation/components/AuthContainer.tsx +0 -80
- package/src/presentation/components/AuthFormCard.tsx +0 -51
- package/src/presentation/components/icons/AppleIconSvg.tsx +0 -24
- package/src/presentation/components/icons/GoogleIconSvg.tsx +0 -32
- package/src/presentation/components/icons/index.ts +0 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-auth",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.8",
|
|
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",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@types/react": "~19.1.0",
|
|
62
62
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
63
63
|
"@typescript-eslint/parser": "^7.0.0",
|
|
64
|
-
"@umituz/react-native-design-system": "^2.8.
|
|
64
|
+
"@umituz/react-native-design-system": "^2.8.39",
|
|
65
65
|
"@umituz/react-native-design-system-theme": "*",
|
|
66
66
|
"eslint": "^8.57.0",
|
|
67
67
|
"expo-apple-authentication": "^6.0.0",
|
package/src/index.ts
CHANGED
|
@@ -168,9 +168,7 @@ export type {
|
|
|
168
168
|
} from './presentation/navigation/AuthNavigator';
|
|
169
169
|
|
|
170
170
|
// COMPONENTS
|
|
171
|
-
export { AuthContainer } from './presentation/components/AuthContainer';
|
|
172
171
|
export { AuthHeader } from './presentation/components/AuthHeader';
|
|
173
|
-
export { AuthFormCard } from './presentation/components/AuthFormCard';
|
|
174
172
|
export { LoginForm } from './presentation/components/LoginForm';
|
|
175
173
|
export { RegisterForm } from './presentation/components/RegisterForm';
|
|
176
174
|
export { AuthLegalLinks } from './presentation/components/AuthLegalLinks';
|
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
AlertInline,
|
|
9
|
+
AlertService,
|
|
10
|
+
AlertMode,
|
|
11
|
+
} from "@umituz/react-native-design-system";
|
|
9
12
|
|
|
10
13
|
interface AuthErrorDisplayProps {
|
|
11
14
|
error: string | null;
|
|
@@ -14,46 +17,20 @@ interface AuthErrorDisplayProps {
|
|
|
14
17
|
export const AuthErrorDisplay: React.FC<AuthErrorDisplayProps> = ({
|
|
15
18
|
error,
|
|
16
19
|
}) => {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
const alert = React.useMemo(() => {
|
|
21
|
+
if (!error) return null;
|
|
22
|
+
return AlertService.createErrorAlert(error, undefined, {
|
|
23
|
+
mode: AlertMode.INLINE,
|
|
24
|
+
});
|
|
25
|
+
}, [error]);
|
|
26
|
+
|
|
27
|
+
if (!alert) {
|
|
20
28
|
return null;
|
|
21
29
|
}
|
|
22
30
|
|
|
23
|
-
return
|
|
24
|
-
<View
|
|
25
|
-
style={[
|
|
26
|
-
styles.errorContainer,
|
|
27
|
-
{
|
|
28
|
-
backgroundColor: tokens.colors.errorLight,
|
|
29
|
-
borderColor: tokens.colors.error,
|
|
30
|
-
},
|
|
31
|
-
]}
|
|
32
|
-
>
|
|
33
|
-
<AtomicText
|
|
34
|
-
type="bodyMedium"
|
|
35
|
-
style={[styles.errorText, { color: tokens.colors.error }]}
|
|
36
|
-
>
|
|
37
|
-
{error}
|
|
38
|
-
</AtomicText>
|
|
39
|
-
</View>
|
|
40
|
-
);
|
|
31
|
+
return <AlertInline alert={alert} />;
|
|
41
32
|
};
|
|
42
33
|
|
|
43
|
-
const styles = StyleSheet.create({
|
|
44
|
-
errorContainer: {
|
|
45
|
-
marginBottom: 16,
|
|
46
|
-
padding: 14,
|
|
47
|
-
borderRadius: 12,
|
|
48
|
-
borderWidth: 1,
|
|
49
|
-
},
|
|
50
|
-
errorText: {
|
|
51
|
-
fontSize: 14,
|
|
52
|
-
textAlign: "center",
|
|
53
|
-
fontWeight: "500",
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
|
|
57
34
|
|
|
58
35
|
|
|
59
36
|
|
|
@@ -21,15 +21,16 @@ export const AuthHeader: React.FC<AuthHeaderProps> = ({ title, subtitle }) => {
|
|
|
21
21
|
<View style={[styles.header, { marginBottom: tokens.spacing.xl, paddingHorizontal: tokens.spacing.md }]}>
|
|
22
22
|
<AtomicText
|
|
23
23
|
type="headlineLarge"
|
|
24
|
-
|
|
24
|
+
color="onPrimary"
|
|
25
|
+
style={{ textAlign: "center" }}
|
|
25
26
|
>
|
|
26
27
|
{title}
|
|
27
28
|
</AtomicText>
|
|
28
29
|
{(subtitle || t("auth.subtitle")) && (
|
|
29
30
|
<AtomicText
|
|
30
31
|
type="bodyMedium"
|
|
32
|
+
color="textInverse"
|
|
31
33
|
style={{
|
|
32
|
-
color: tokens.colors.textInverse,
|
|
33
34
|
textAlign: "center",
|
|
34
35
|
marginTop: tokens.spacing.xs,
|
|
35
36
|
}}
|
|
@@ -79,13 +79,11 @@ export const AuthLegalLinks: React.FC<AuthLegalLinksProps> = ({
|
|
|
79
79
|
{hasTerms && (
|
|
80
80
|
<AtomicButton
|
|
81
81
|
variant="text"
|
|
82
|
+
size="sm"
|
|
82
83
|
onPress={handleTermsPress}
|
|
83
84
|
style={styles.linkButton}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
{t("auth.termsOfService")}
|
|
87
|
-
</AtomicText>
|
|
88
|
-
</AtomicButton>
|
|
85
|
+
title={t("auth.termsOfService")}
|
|
86
|
+
/>
|
|
89
87
|
)}
|
|
90
88
|
{hasTerms && hasPrivacy && (
|
|
91
89
|
<AtomicText type="bodySmall" color="secondary" style={styles.separator}>
|
|
@@ -95,13 +93,11 @@ export const AuthLegalLinks: React.FC<AuthLegalLinksProps> = ({
|
|
|
95
93
|
{hasPrivacy && (
|
|
96
94
|
<AtomicButton
|
|
97
95
|
variant="text"
|
|
96
|
+
size="sm"
|
|
98
97
|
onPress={handlePrivacyPress}
|
|
99
98
|
style={styles.linkButton}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
{t("auth.privacyPolicy")}
|
|
103
|
-
</AtomicText>
|
|
104
|
-
</AtomicButton>
|
|
99
|
+
title={t("auth.privacyPolicy")}
|
|
100
|
+
/>
|
|
105
101
|
)}
|
|
106
102
|
</View>
|
|
107
103
|
</View>
|
|
@@ -49,56 +49,52 @@ export const LoginForm: React.FC<LoginFormProps> = ({ onNavigateToRegister }) =>
|
|
|
49
49
|
|
|
50
50
|
return (
|
|
51
51
|
<>
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
</View>
|
|
52
|
+
<AtomicInput
|
|
53
|
+
label={t("auth.email")}
|
|
54
|
+
value={email}
|
|
55
|
+
onChangeText={handleEmailChange}
|
|
56
|
+
placeholder={t("auth.emailPlaceholder")}
|
|
57
|
+
keyboardType="email-address"
|
|
58
|
+
autoCapitalize="none"
|
|
59
|
+
disabled={loading}
|
|
60
|
+
state={emailError ? "error" : "default"}
|
|
61
|
+
helperText={emailError || undefined}
|
|
62
|
+
returnKeyType="next"
|
|
63
|
+
onSubmitEditing={() => passwordRef.current?.focus()}
|
|
64
|
+
blurOnSubmit={false}
|
|
65
|
+
textContentType="emailAddress"
|
|
66
|
+
style={styles.input}
|
|
67
|
+
/>
|
|
69
68
|
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
</View>
|
|
69
|
+
<AtomicInput
|
|
70
|
+
ref={passwordRef}
|
|
71
|
+
label={t("auth.password")}
|
|
72
|
+
value={password}
|
|
73
|
+
onChangeText={handlePasswordChange}
|
|
74
|
+
placeholder={t("auth.passwordPlaceholder")}
|
|
75
|
+
secureTextEntry
|
|
76
|
+
showPasswordToggle
|
|
77
|
+
autoCapitalize="none"
|
|
78
|
+
disabled={loading}
|
|
79
|
+
state={passwordError ? "error" : "default"}
|
|
80
|
+
helperText={passwordError || undefined}
|
|
81
|
+
returnKeyType="done"
|
|
82
|
+
onSubmitEditing={() => { void handleSignIn(); }}
|
|
83
|
+
textContentType="password"
|
|
84
|
+
style={styles.input}
|
|
85
|
+
/>
|
|
88
86
|
|
|
89
87
|
<AuthErrorDisplay error={displayError} />
|
|
90
88
|
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
</AtomicButton>
|
|
101
|
-
</View>
|
|
89
|
+
<AtomicButton
|
|
90
|
+
variant="primary"
|
|
91
|
+
onPress={() => { void handleSignIn(); }}
|
|
92
|
+
disabled={loading || !email.trim() || !password.trim()}
|
|
93
|
+
fullWidth
|
|
94
|
+
style={styles.signInButton}
|
|
95
|
+
>
|
|
96
|
+
{t("auth.signIn")}
|
|
97
|
+
</AtomicButton>
|
|
102
98
|
|
|
103
99
|
<AuthLink
|
|
104
100
|
text={t("auth.dontHaveAccount")}
|
|
@@ -111,13 +107,11 @@ export const LoginForm: React.FC<LoginFormProps> = ({ onNavigateToRegister }) =>
|
|
|
111
107
|
};
|
|
112
108
|
|
|
113
109
|
const styles = StyleSheet.create({
|
|
114
|
-
|
|
110
|
+
input: {
|
|
115
111
|
marginBottom: 20,
|
|
116
112
|
},
|
|
117
|
-
buttonContainer: {
|
|
118
|
-
marginBottom: 16,
|
|
119
|
-
},
|
|
120
113
|
signInButton: {
|
|
121
114
|
minHeight: 52,
|
|
115
|
+
marginBottom: 16,
|
|
122
116
|
},
|
|
123
117
|
});
|
|
@@ -53,108 +53,102 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
|
|
53
53
|
|
|
54
54
|
return (
|
|
55
55
|
<>
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
</View>
|
|
56
|
+
<AtomicInput
|
|
57
|
+
label={t("auth.displayName")}
|
|
58
|
+
value={displayName}
|
|
59
|
+
onChangeText={handleDisplayNameChange}
|
|
60
|
+
placeholder={
|
|
61
|
+
t("auth.displayNamePlaceholder")
|
|
62
|
+
}
|
|
63
|
+
autoCapitalize="words"
|
|
64
|
+
disabled={loading}
|
|
65
|
+
state={fieldErrors.displayName ? "error" : "default"}
|
|
66
|
+
helperText={fieldErrors.displayName || undefined}
|
|
67
|
+
returnKeyType="next"
|
|
68
|
+
onSubmitEditing={() => emailRef.current?.focus()}
|
|
69
|
+
blurOnSubmit={false}
|
|
70
|
+
style={styles.input}
|
|
71
|
+
/>
|
|
73
72
|
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
</View>
|
|
73
|
+
<AtomicInput
|
|
74
|
+
ref={emailRef}
|
|
75
|
+
label={t("auth.email")}
|
|
76
|
+
value={email}
|
|
77
|
+
onChangeText={handleEmailChange}
|
|
78
|
+
placeholder={t("auth.emailPlaceholder")}
|
|
79
|
+
keyboardType="email-address"
|
|
80
|
+
autoCapitalize="none"
|
|
81
|
+
disabled={loading}
|
|
82
|
+
state={fieldErrors.email ? "error" : "default"}
|
|
83
|
+
helperText={fieldErrors.email || undefined}
|
|
84
|
+
returnKeyType="next"
|
|
85
|
+
onSubmitEditing={() => passwordRef.current?.focus()}
|
|
86
|
+
blurOnSubmit={false}
|
|
87
|
+
textContentType="emailAddress"
|
|
88
|
+
style={styles.input}
|
|
89
|
+
/>
|
|
92
90
|
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
</View>
|
|
91
|
+
<AtomicInput
|
|
92
|
+
ref={passwordRef}
|
|
93
|
+
label={t("auth.password")}
|
|
94
|
+
value={password}
|
|
95
|
+
onChangeText={handlePasswordChange}
|
|
96
|
+
placeholder={t("auth.passwordPlaceholder")}
|
|
97
|
+
secureTextEntry
|
|
98
|
+
showPasswordToggle
|
|
99
|
+
autoCapitalize="none"
|
|
100
|
+
disabled={loading}
|
|
101
|
+
state={fieldErrors.password ? "error" : "default"}
|
|
102
|
+
helperText={fieldErrors.password || undefined}
|
|
103
|
+
returnKeyType="next"
|
|
104
|
+
onSubmitEditing={() => confirmPasswordRef.current?.focus()}
|
|
105
|
+
blurOnSubmit={false}
|
|
106
|
+
textContentType="newPassword"
|
|
107
|
+
style={styles.input}
|
|
108
|
+
/>
|
|
109
|
+
{password.length > 0 && (
|
|
110
|
+
<PasswordStrengthIndicator requirements={passwordRequirements} />
|
|
111
|
+
)}
|
|
115
112
|
|
|
116
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
</View>
|
|
113
|
+
<AtomicInput
|
|
114
|
+
ref={confirmPasswordRef}
|
|
115
|
+
label={t("auth.confirmPassword")}
|
|
116
|
+
value={confirmPassword}
|
|
117
|
+
onChangeText={handleConfirmPasswordChange}
|
|
118
|
+
placeholder={
|
|
119
|
+
t("auth.confirmPasswordPlaceholder")
|
|
120
|
+
}
|
|
121
|
+
secureTextEntry
|
|
122
|
+
showPasswordToggle
|
|
123
|
+
autoCapitalize="none"
|
|
124
|
+
disabled={loading}
|
|
125
|
+
state={fieldErrors.confirmPassword ? "error" : "default"}
|
|
126
|
+
helperText={fieldErrors.confirmPassword || undefined}
|
|
127
|
+
returnKeyType="done"
|
|
128
|
+
onSubmitEditing={() => { void handleSignUp(); }}
|
|
129
|
+
textContentType="newPassword"
|
|
130
|
+
style={styles.input}
|
|
131
|
+
/>
|
|
132
|
+
{confirmPassword.length > 0 && (
|
|
133
|
+
<PasswordMatchIndicator isMatch={passwordsMatch} />
|
|
134
|
+
)}
|
|
139
135
|
|
|
140
136
|
<AuthErrorDisplay error={displayError} />
|
|
141
137
|
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
</AtomicButton>
|
|
157
|
-
</View>
|
|
138
|
+
<AtomicButton
|
|
139
|
+
variant="primary"
|
|
140
|
+
onPress={() => { void handleSignUp(); }}
|
|
141
|
+
disabled={
|
|
142
|
+
loading ||
|
|
143
|
+
!email.trim() ||
|
|
144
|
+
!password.trim() ||
|
|
145
|
+
!confirmPassword.trim()
|
|
146
|
+
}
|
|
147
|
+
fullWidth
|
|
148
|
+
style={styles.signUpButton}
|
|
149
|
+
>
|
|
150
|
+
{t("auth.signUp")}
|
|
151
|
+
</AtomicButton>
|
|
158
152
|
|
|
159
153
|
<AuthLink
|
|
160
154
|
text={t("auth.alreadyHaveAccount")}
|
|
@@ -175,15 +169,13 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
|
|
175
169
|
};
|
|
176
170
|
|
|
177
171
|
const styles = StyleSheet.create({
|
|
178
|
-
|
|
172
|
+
input: {
|
|
179
173
|
marginBottom: 20,
|
|
180
174
|
},
|
|
181
|
-
buttonContainer: {
|
|
182
|
-
marginBottom: 16,
|
|
183
|
-
marginTop: 8,
|
|
184
|
-
},
|
|
185
175
|
signUpButton: {
|
|
186
176
|
minHeight: 52,
|
|
177
|
+
marginBottom: 16,
|
|
178
|
+
marginTop: 8,
|
|
187
179
|
},
|
|
188
180
|
});
|
|
189
181
|
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
|
-
TouchableOpacity,
|
|
5
4
|
StyleSheet,
|
|
6
5
|
Platform,
|
|
7
6
|
} from "react-native";
|
|
8
|
-
import { useAppDesignTokens,
|
|
7
|
+
import { useAppDesignTokens, Divider, AtomicButton } from "@umituz/react-native-design-system";
|
|
9
8
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
10
9
|
import type { SocialAuthProvider } from "../../domain/value-objects/AuthConfig";
|
|
11
10
|
|
|
@@ -45,61 +44,35 @@ export const SocialLoginButtons: React.FC<SocialLoginButtonsProps> = ({
|
|
|
45
44
|
|
|
46
45
|
return (
|
|
47
46
|
<View style={styles.container}>
|
|
48
|
-
<
|
|
49
|
-
<View style={[styles.divider, { backgroundColor: tokens.colors.border }]} />
|
|
50
|
-
<AtomicText type="bodySmall" color="secondary" style={styles.dividerText}>
|
|
51
|
-
{t("auth.orContinueWith")}
|
|
52
|
-
</AtomicText>
|
|
53
|
-
<View style={[styles.divider, { backgroundColor: tokens.colors.border }]} />
|
|
54
|
-
</View>
|
|
47
|
+
<Divider text={t("auth.orContinueWith")} spacing="large" />
|
|
55
48
|
|
|
56
49
|
<View style={styles.buttonsContainer}>
|
|
57
50
|
{showGoogle && (
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
activeOpacity={0.7}
|
|
51
|
+
<AtomicButton
|
|
52
|
+
variant="outline"
|
|
53
|
+
onPress={onGooglePress || (() => {})}
|
|
54
|
+
loading={googleLoading}
|
|
55
|
+
disabled={disabled}
|
|
56
|
+
icon="logo-google"
|
|
57
|
+
fullWidth
|
|
58
|
+
style={styles.socialButton}
|
|
67
59
|
>
|
|
68
|
-
{
|
|
69
|
-
|
|
70
|
-
) : (
|
|
71
|
-
<>
|
|
72
|
-
<AtomicIcon name="logo-google" size="sm" />
|
|
73
|
-
<AtomicText style={[styles.buttonText, { color: tokens.colors.textPrimary }]}>
|
|
74
|
-
{t("auth.google")}
|
|
75
|
-
</AtomicText>
|
|
76
|
-
</>
|
|
77
|
-
)}
|
|
78
|
-
</TouchableOpacity>
|
|
60
|
+
{t("auth.google")}
|
|
61
|
+
</AtomicButton>
|
|
79
62
|
)}
|
|
80
63
|
|
|
81
64
|
{showApple && (
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
activeOpacity={0.7}
|
|
65
|
+
<AtomicButton
|
|
66
|
+
variant="outline"
|
|
67
|
+
onPress={onApplePress || (() => {})}
|
|
68
|
+
loading={appleLoading}
|
|
69
|
+
disabled={disabled}
|
|
70
|
+
icon="logo-apple"
|
|
71
|
+
fullWidth
|
|
72
|
+
style={styles.socialButton}
|
|
91
73
|
>
|
|
92
|
-
{
|
|
93
|
-
|
|
94
|
-
) : (
|
|
95
|
-
<>
|
|
96
|
-
<AtomicIcon name="logo-apple" size="sm" color="onSurface" />
|
|
97
|
-
<AtomicText style={[styles.buttonText, { color: tokens.colors.textPrimary }]}>
|
|
98
|
-
{t("auth.apple")}
|
|
99
|
-
</AtomicText>
|
|
100
|
-
</>
|
|
101
|
-
)}
|
|
102
|
-
</TouchableOpacity>
|
|
74
|
+
{t("auth.apple")}
|
|
75
|
+
</AtomicButton>
|
|
103
76
|
)}
|
|
104
77
|
</View>
|
|
105
78
|
</View>
|
|
@@ -110,38 +83,13 @@ const styles = StyleSheet.create({
|
|
|
110
83
|
container: {
|
|
111
84
|
marginTop: 24,
|
|
112
85
|
},
|
|
113
|
-
dividerContainer: {
|
|
114
|
-
flexDirection: "row",
|
|
115
|
-
alignItems: "center",
|
|
116
|
-
marginBottom: 20,
|
|
117
|
-
},
|
|
118
|
-
divider: {
|
|
119
|
-
flex: 1,
|
|
120
|
-
height: 1,
|
|
121
|
-
},
|
|
122
|
-
dividerText: {
|
|
123
|
-
marginHorizontal: 16,
|
|
124
|
-
},
|
|
125
86
|
buttonsContainer: {
|
|
126
|
-
flexDirection:
|
|
87
|
+
flexDirection: 'row',
|
|
88
|
+
marginTop: 8,
|
|
127
89
|
gap: 12,
|
|
128
90
|
},
|
|
129
91
|
socialButton: {
|
|
130
92
|
flex: 1,
|
|
131
|
-
flexDirection: "row",
|
|
132
|
-
alignItems: "center",
|
|
133
|
-
justifyContent: "center",
|
|
134
|
-
paddingVertical: 14,
|
|
135
|
-
borderRadius: 12,
|
|
136
|
-
borderWidth: 1,
|
|
137
|
-
gap: 8,
|
|
138
|
-
},
|
|
139
|
-
disabledButton: {
|
|
140
|
-
opacity: 0.5,
|
|
141
|
-
},
|
|
142
|
-
buttonText: {
|
|
143
|
-
fontSize: 16,
|
|
144
|
-
fontWeight: "600",
|
|
145
93
|
},
|
|
146
94
|
});
|
|
147
95
|
|
|
@@ -4,29 +4,34 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
|
-
import { useAppNavigation } from "@umituz/react-native-design-system";
|
|
7
|
+
import { useAppNavigation, AtomicCard, ScreenLayout, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
8
8
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
9
9
|
import type { AuthStackParamList } from "../navigation/AuthNavigator";
|
|
10
|
-
import { AuthContainer } from "../components/AuthContainer";
|
|
11
10
|
import { AuthHeader } from "../components/AuthHeader";
|
|
12
|
-
import { AuthFormCard } from "../components/AuthFormCard";
|
|
13
11
|
import { LoginForm } from "../components/LoginForm";
|
|
14
12
|
|
|
15
13
|
export const LoginScreen: React.FC = () => {
|
|
16
14
|
const { t } = useLocalization();
|
|
17
15
|
const navigation = useAppNavigation<AuthStackParamList>();
|
|
16
|
+
const tokens = useAppDesignTokens();
|
|
18
17
|
|
|
19
18
|
const handleNavigateToRegister = () => {
|
|
20
19
|
navigation.navigate("Register");
|
|
21
20
|
};
|
|
22
21
|
|
|
23
22
|
return (
|
|
24
|
-
<
|
|
23
|
+
<ScreenLayout
|
|
24
|
+
scrollable
|
|
25
|
+
keyboardAvoiding
|
|
26
|
+
maxWidth={440}
|
|
27
|
+
contentContainerStyle={{ justifyContent: "center" }}
|
|
28
|
+
backgroundColor={tokens.colors.backgroundPrimary}
|
|
29
|
+
>
|
|
25
30
|
<AuthHeader title={t("auth.title")} />
|
|
26
|
-
<
|
|
31
|
+
<AtomicCard variant="elevated" padding="lg">
|
|
27
32
|
<LoginForm onNavigateToRegister={handleNavigateToRegister} />
|
|
28
|
-
</
|
|
29
|
-
</
|
|
33
|
+
</AtomicCard>
|
|
34
|
+
</ScreenLayout>
|
|
30
35
|
);
|
|
31
36
|
};
|
|
32
37
|
|
|
@@ -4,12 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import React from "react";
|
|
7
|
-
import { useAppNavigation } from "@umituz/react-native-design-system";
|
|
7
|
+
import { useAppNavigation, AtomicCard, ScreenLayout, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
8
8
|
import { useLocalization } from "@umituz/react-native-localization";
|
|
9
9
|
import type { AuthStackParamList } from "../navigation/AuthNavigator";
|
|
10
|
-
import { AuthContainer } from "../components/AuthContainer";
|
|
11
10
|
import { AuthHeader } from "../components/AuthHeader";
|
|
12
|
-
import { AuthFormCard } from "../components/AuthFormCard";
|
|
13
11
|
import { RegisterForm } from "../components/RegisterForm";
|
|
14
12
|
|
|
15
13
|
export interface RegisterScreenProps {
|
|
@@ -27,15 +25,22 @@ export const RegisterScreen: React.FC<RegisterScreenProps> = ({
|
|
|
27
25
|
}) => {
|
|
28
26
|
const { t } = useLocalization();
|
|
29
27
|
const navigation = useAppNavigation<AuthStackParamList>();
|
|
28
|
+
const tokens = useAppDesignTokens();
|
|
30
29
|
|
|
31
30
|
const handleNavigateToLogin = () => {
|
|
32
31
|
navigation.navigate("Login");
|
|
33
32
|
};
|
|
34
33
|
|
|
35
34
|
return (
|
|
36
|
-
<
|
|
35
|
+
<ScreenLayout
|
|
36
|
+
scrollable
|
|
37
|
+
keyboardAvoiding
|
|
38
|
+
maxWidth={440}
|
|
39
|
+
contentContainerStyle={{ justifyContent: "center" }}
|
|
40
|
+
backgroundColor={tokens.colors.backgroundPrimary}
|
|
41
|
+
>
|
|
37
42
|
<AuthHeader title={t("auth.createAccount")} />
|
|
38
|
-
<
|
|
43
|
+
<AtomicCard variant="elevated" padding="lg">
|
|
39
44
|
<RegisterForm
|
|
40
45
|
onNavigateToLogin={handleNavigateToLogin}
|
|
41
46
|
termsUrl={termsUrl}
|
|
@@ -43,8 +48,8 @@ export const RegisterScreen: React.FC<RegisterScreenProps> = ({
|
|
|
43
48
|
onTermsPress={onTermsPress}
|
|
44
49
|
onPrivacyPress={onPrivacyPress}
|
|
45
50
|
/>
|
|
46
|
-
</
|
|
47
|
-
</
|
|
51
|
+
</AtomicCard>
|
|
52
|
+
</ScreenLayout>
|
|
48
53
|
);
|
|
49
54
|
};
|
|
50
55
|
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth Background Component
|
|
3
|
-
* Standard background for auth screens
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React from "react";
|
|
7
|
-
import { StyleSheet, View } from "react-native";
|
|
8
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
|
-
|
|
10
|
-
export const AuthBackground: React.FC = () => {
|
|
11
|
-
const tokens = useAppDesignTokens();
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<View
|
|
15
|
-
style={[
|
|
16
|
-
StyleSheet.absoluteFill,
|
|
17
|
-
{ backgroundColor: tokens.colors.backgroundPrimary }
|
|
18
|
-
]}
|
|
19
|
-
/>
|
|
20
|
-
);
|
|
21
|
-
};
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth Container Component
|
|
3
|
-
* Main container for auth screens with background and scroll
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { useMemo } from "react";
|
|
7
|
-
import {
|
|
8
|
-
View,
|
|
9
|
-
StyleSheet,
|
|
10
|
-
ScrollView,
|
|
11
|
-
KeyboardAvoidingView,
|
|
12
|
-
} from "react-native";
|
|
13
|
-
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
14
|
-
import { useResponsive } from "@umituz/react-native-design-system";
|
|
15
|
-
import { AuthBackground } from "./AuthBackground";
|
|
16
|
-
|
|
17
|
-
/** Layout constants for auth screens */
|
|
18
|
-
const AUTH_LAYOUT = {
|
|
19
|
-
VERTICAL_PADDING: 40,
|
|
20
|
-
HORIZONTAL_PADDING: 20,
|
|
21
|
-
MAX_CONTENT_WIDTH: 440,
|
|
22
|
-
} as const;
|
|
23
|
-
|
|
24
|
-
interface AuthContainerProps {
|
|
25
|
-
children: React.ReactNode;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const AuthContainer: React.FC<AuthContainerProps> = ({ children }) => {
|
|
29
|
-
const insets = useSafeAreaInsets();
|
|
30
|
-
const { spacingMultiplier } = useResponsive();
|
|
31
|
-
|
|
32
|
-
const dynamicStyles = useMemo(() => ({
|
|
33
|
-
paddingTop: insets.top + (AUTH_LAYOUT.VERTICAL_PADDING * spacingMultiplier),
|
|
34
|
-
paddingBottom: insets.bottom + (AUTH_LAYOUT.VERTICAL_PADDING * spacingMultiplier),
|
|
35
|
-
}), [insets.top, insets.bottom, spacingMultiplier]);
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<KeyboardAvoidingView
|
|
39
|
-
style={styles.container}
|
|
40
|
-
behavior="padding"
|
|
41
|
-
>
|
|
42
|
-
<AuthBackground />
|
|
43
|
-
<ScrollView
|
|
44
|
-
contentContainerStyle={[styles.scrollContent, dynamicStyles]}
|
|
45
|
-
keyboardShouldPersistTaps="handled"
|
|
46
|
-
showsVerticalScrollIndicator={false}
|
|
47
|
-
>
|
|
48
|
-
<View style={styles.content}>{children}</View>
|
|
49
|
-
</ScrollView>
|
|
50
|
-
</KeyboardAvoidingView>
|
|
51
|
-
);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const styles = StyleSheet.create({
|
|
55
|
-
container: {
|
|
56
|
-
flex: 1,
|
|
57
|
-
},
|
|
58
|
-
scrollContent: {
|
|
59
|
-
flexGrow: 1,
|
|
60
|
-
paddingHorizontal: AUTH_LAYOUT.HORIZONTAL_PADDING,
|
|
61
|
-
},
|
|
62
|
-
content: {
|
|
63
|
-
flex: 1,
|
|
64
|
-
justifyContent: "center",
|
|
65
|
-
maxWidth: AUTH_LAYOUT.MAX_CONTENT_WIDTH,
|
|
66
|
-
alignSelf: "center",
|
|
67
|
-
width: "100%",
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth Form Card Component
|
|
3
|
-
* Reusable card container for auth forms
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React from "react";
|
|
7
|
-
import { View, StyleSheet } from "react-native";
|
|
8
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
|
-
|
|
10
|
-
interface AuthFormCardProps {
|
|
11
|
-
children: React.ReactNode;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const AuthFormCard: React.FC<AuthFormCardProps> = ({ children }) => {
|
|
15
|
-
const tokens = useAppDesignTokens();
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<View
|
|
19
|
-
style={[
|
|
20
|
-
styles.formCard,
|
|
21
|
-
{
|
|
22
|
-
backgroundColor: tokens.colors.surface,
|
|
23
|
-
borderRadius: tokens.borders.radius.xl,
|
|
24
|
-
padding: tokens.spacing.lg,
|
|
25
|
-
},
|
|
26
|
-
]}
|
|
27
|
-
>
|
|
28
|
-
<View style={styles.form}>{children}</View>
|
|
29
|
-
</View>
|
|
30
|
-
);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const styles = StyleSheet.create({
|
|
34
|
-
formCard: {
|
|
35
|
-
width: "100%",
|
|
36
|
-
},
|
|
37
|
-
form: {
|
|
38
|
-
width: "100%",
|
|
39
|
-
},
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Apple Icon SVG
|
|
3
|
-
* Dark mode compatible Apple logo
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React from "react";
|
|
7
|
-
import Svg, { Path } from "react-native-svg";
|
|
8
|
-
|
|
9
|
-
export interface AppleIconSvgProps {
|
|
10
|
-
size?: number;
|
|
11
|
-
color: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const AppleIconSvg: React.FC<AppleIconSvgProps> = ({
|
|
15
|
-
size = 20,
|
|
16
|
-
color,
|
|
17
|
-
}) => (
|
|
18
|
-
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
19
|
-
<Path
|
|
20
|
-
d="M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.53 4.09l-.02-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z"
|
|
21
|
-
fill={color}
|
|
22
|
-
/>
|
|
23
|
-
</Svg>
|
|
24
|
-
);
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Google Icon SVG
|
|
3
|
-
* Standard Google logo with color
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React from "react";
|
|
7
|
-
import Svg, { Path } from "react-native-svg";
|
|
8
|
-
|
|
9
|
-
export interface GoogleIconSvgProps {
|
|
10
|
-
size?: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const GoogleIconSvg: React.FC<GoogleIconSvgProps> = ({ size = 20 }) => (
|
|
14
|
-
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
|
|
15
|
-
<Path
|
|
16
|
-
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
|
17
|
-
fill="#4285F4"
|
|
18
|
-
/>
|
|
19
|
-
<Path
|
|
20
|
-
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
|
21
|
-
fill="#34A853"
|
|
22
|
-
/>
|
|
23
|
-
<Path
|
|
24
|
-
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
|
25
|
-
fill="#FBBC05"
|
|
26
|
-
/>
|
|
27
|
-
<Path
|
|
28
|
-
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
|
29
|
-
fill="#EA4335"
|
|
30
|
-
/>
|
|
31
|
-
</Svg>
|
|
32
|
-
);
|