@umituz/react-native-auth 3.6.44 → 3.6.45
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 -6
- package/src/index.ts +16 -6
- package/src/presentation/components/AccountActions.tsx +137 -146
- package/src/presentation/components/AuthBottomSheet.tsx +53 -47
- package/src/presentation/components/AuthHeader.tsx +3 -21
- package/src/presentation/components/AuthLegalLinks.tsx +31 -77
- package/src/presentation/components/LoginForm.tsx +23 -33
- package/src/presentation/components/PasswordMatchIndicator.tsx +8 -7
- package/src/presentation/components/PasswordStrengthIndicator.tsx +16 -20
- package/src/presentation/components/RegisterForm.tsx +45 -28
- package/src/presentation/components/SocialLoginButtons.tsx +45 -39
- package/src/presentation/hooks/useLoginForm.ts +28 -18
- package/src/presentation/hooks/useRegisterForm.ts +32 -46
- package/src/presentation/navigation/AuthNavigator.tsx +20 -20
- package/src/presentation/screens/ChangePasswordScreen.tsx +58 -57
- package/src/presentation/screens/LoginScreen.tsx +17 -12
- package/src/presentation/screens/RegisterScreen.tsx +16 -15
- package/src/types/translations.types.ts +89 -0
|
@@ -1,103 +1,79 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth Legal Links Component
|
|
3
|
-
* Display Terms of Service and Privacy Policy links
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import React from "react";
|
|
7
2
|
import { View, StyleSheet, Linking } from "react-native";
|
|
8
|
-
import { AtomicButton,
|
|
3
|
+
import { AtomicText, AtomicButton, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
4
|
|
|
10
|
-
|
|
5
|
+
export interface AuthLegalLinksTranslations {
|
|
6
|
+
termsOfService: string;
|
|
7
|
+
privacyPolicy: string;
|
|
8
|
+
}
|
|
11
9
|
|
|
12
10
|
export interface AuthLegalLinksProps {
|
|
13
|
-
|
|
14
|
-
* Terms of Service URL
|
|
15
|
-
*/
|
|
11
|
+
translations: AuthLegalLinksTranslations;
|
|
16
12
|
termsUrl?: string;
|
|
17
|
-
/**
|
|
18
|
-
* Privacy Policy URL
|
|
19
|
-
*/
|
|
20
13
|
privacyUrl?: string;
|
|
21
|
-
/**
|
|
22
|
-
* Callback when Terms of Service is pressed
|
|
23
|
-
*/
|
|
24
14
|
onTermsPress?: () => void;
|
|
25
|
-
/**
|
|
26
|
-
* Callback when Privacy Policy is pressed
|
|
27
|
-
*/
|
|
28
15
|
onPrivacyPress?: () => void;
|
|
29
|
-
/**
|
|
30
|
-
* Custom text before links
|
|
31
|
-
*/
|
|
32
16
|
prefixText?: string;
|
|
33
17
|
}
|
|
34
18
|
|
|
35
19
|
export const AuthLegalLinks: React.FC<AuthLegalLinksProps> = ({
|
|
20
|
+
translations,
|
|
36
21
|
termsUrl,
|
|
37
22
|
privacyUrl,
|
|
38
23
|
onTermsPress,
|
|
39
24
|
onPrivacyPress,
|
|
40
25
|
prefixText,
|
|
41
26
|
}) => {
|
|
42
|
-
const
|
|
27
|
+
const tokens = useAppDesignTokens();
|
|
43
28
|
|
|
44
|
-
const handleTermsPress = () => {
|
|
29
|
+
const handleTermsPress = async () => {
|
|
45
30
|
if (onTermsPress) {
|
|
46
31
|
onTermsPress();
|
|
47
32
|
} else if (termsUrl) {
|
|
48
|
-
|
|
33
|
+
await Linking.openURL(termsUrl);
|
|
49
34
|
}
|
|
50
35
|
};
|
|
51
36
|
|
|
52
|
-
const handlePrivacyPress = () => {
|
|
37
|
+
const handlePrivacyPress = async () => {
|
|
53
38
|
if (onPrivacyPress) {
|
|
54
39
|
onPrivacyPress();
|
|
55
40
|
} else if (privacyUrl) {
|
|
56
|
-
|
|
41
|
+
await Linking.openURL(privacyUrl);
|
|
57
42
|
}
|
|
58
43
|
};
|
|
59
44
|
|
|
60
|
-
|
|
61
|
-
const hasPrivacy = privacyUrl || onPrivacyPress;
|
|
62
|
-
|
|
63
|
-
if (!hasTerms && !hasPrivacy) {
|
|
45
|
+
if (!termsUrl && !privacyUrl && !onTermsPress && !onPrivacyPress) {
|
|
64
46
|
return null;
|
|
65
47
|
}
|
|
66
48
|
|
|
67
49
|
return (
|
|
68
|
-
<View style={styles.container}>
|
|
50
|
+
<View style={[styles.container, { marginTop: tokens.spacing.lg }]}>
|
|
69
51
|
{prefixText && (
|
|
70
|
-
<AtomicText
|
|
71
|
-
type="bodySmall"
|
|
72
|
-
color="textSecondary"
|
|
73
|
-
style={styles.prefixText}
|
|
74
|
-
>
|
|
52
|
+
<AtomicText type="bodySmall" color="textSecondary" style={styles.prefix}>
|
|
75
53
|
{prefixText}
|
|
76
54
|
</AtomicText>
|
|
77
55
|
)}
|
|
78
|
-
<View style={styles.
|
|
79
|
-
{
|
|
56
|
+
<View style={styles.linksRow}>
|
|
57
|
+
{(termsUrl || onTermsPress) && (
|
|
80
58
|
<AtomicButton
|
|
81
59
|
variant="text"
|
|
82
60
|
size="sm"
|
|
83
|
-
onPress={handleTermsPress}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
61
|
+
onPress={() => { void handleTermsPress(); }}
|
|
62
|
+
>
|
|
63
|
+
{translations.termsOfService}
|
|
64
|
+
</AtomicButton>
|
|
87
65
|
)}
|
|
88
|
-
{
|
|
89
|
-
<AtomicText type="bodySmall" color="textSecondary"
|
|
90
|
-
{" • "}
|
|
91
|
-
</AtomicText>
|
|
66
|
+
{(termsUrl || onTermsPress) && (privacyUrl || onPrivacyPress) && (
|
|
67
|
+
<AtomicText type="bodySmall" color="textSecondary"> & </AtomicText>
|
|
92
68
|
)}
|
|
93
|
-
{
|
|
69
|
+
{(privacyUrl || onPrivacyPress) && (
|
|
94
70
|
<AtomicButton
|
|
95
71
|
variant="text"
|
|
96
72
|
size="sm"
|
|
97
|
-
onPress={handlePrivacyPress}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
73
|
+
onPress={() => { void handlePrivacyPress(); }}
|
|
74
|
+
>
|
|
75
|
+
{translations.privacyPolicy}
|
|
76
|
+
</AtomicButton>
|
|
101
77
|
)}
|
|
102
78
|
</View>
|
|
103
79
|
</View>
|
|
@@ -106,35 +82,13 @@ export const AuthLegalLinks: React.FC<AuthLegalLinksProps> = ({
|
|
|
106
82
|
|
|
107
83
|
const styles = StyleSheet.create({
|
|
108
84
|
container: {
|
|
109
|
-
marginTop: 16,
|
|
110
85
|
alignItems: "center",
|
|
111
86
|
},
|
|
112
|
-
|
|
113
|
-
marginBottom:
|
|
114
|
-
textAlign: "center",
|
|
87
|
+
prefix: {
|
|
88
|
+
marginBottom: 4,
|
|
115
89
|
},
|
|
116
|
-
|
|
90
|
+
linksRow: {
|
|
117
91
|
flexDirection: "row",
|
|
118
92
|
alignItems: "center",
|
|
119
|
-
justifyContent: "center",
|
|
120
|
-
flexWrap: "wrap",
|
|
121
|
-
},
|
|
122
|
-
linkButton: {
|
|
123
|
-
paddingHorizontal: 4,
|
|
124
|
-
paddingVertical: 4,
|
|
125
|
-
},
|
|
126
|
-
separator: {
|
|
127
|
-
marginHorizontal: 4,
|
|
128
93
|
},
|
|
129
94
|
});
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
@@ -1,24 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
* Login Form Component
|
|
3
|
-
* Single Responsibility: Render login form UI
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React, { useRef, useEffect } from "react";
|
|
1
|
+
import React, { useRef } from "react";
|
|
7
2
|
import { StyleSheet, TextInput } from "react-native";
|
|
8
3
|
import { AtomicInput, AtomicButton } from "@umituz/react-native-design-system";
|
|
9
|
-
import { useLocalization } from "@umituz/react-native-localization";
|
|
10
4
|
import { useLoginForm } from "../hooks/useLoginForm";
|
|
11
5
|
import { AuthErrorDisplay } from "./AuthErrorDisplay";
|
|
12
6
|
import { AuthLink } from "./AuthLink";
|
|
13
7
|
|
|
14
|
-
|
|
8
|
+
export interface LoginFormTranslations {
|
|
9
|
+
email: string;
|
|
10
|
+
emailPlaceholder: string;
|
|
11
|
+
password: string;
|
|
12
|
+
passwordPlaceholder: string;
|
|
13
|
+
signIn: string;
|
|
14
|
+
dontHaveAccount: string;
|
|
15
|
+
createAccount: string;
|
|
16
|
+
}
|
|
15
17
|
|
|
16
|
-
interface LoginFormProps {
|
|
18
|
+
export interface LoginFormProps {
|
|
19
|
+
translations: LoginFormTranslations;
|
|
17
20
|
onNavigateToRegister: () => void;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
|
-
export const LoginForm: React.FC<LoginFormProps> = ({
|
|
21
|
-
|
|
23
|
+
export const LoginForm: React.FC<LoginFormProps> = ({
|
|
24
|
+
translations,
|
|
25
|
+
onNavigateToRegister,
|
|
26
|
+
}) => {
|
|
22
27
|
const passwordRef = useRef<React.ElementRef<typeof TextInput>>(null);
|
|
23
28
|
const {
|
|
24
29
|
email,
|
|
@@ -32,28 +37,13 @@ export const LoginForm: React.FC<LoginFormProps> = ({ onNavigateToRegister }) =>
|
|
|
32
37
|
displayError,
|
|
33
38
|
} = useLoginForm();
|
|
34
39
|
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (__DEV__) {
|
|
37
|
-
console.log("[LoginForm] Mounted/Updated:", {
|
|
38
|
-
hasEmail: !!email,
|
|
39
|
-
hasPassword: !!password,
|
|
40
|
-
loading,
|
|
41
|
-
hasError: !!displayError,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}, [email, password, loading, displayError]);
|
|
45
|
-
|
|
46
|
-
if (__DEV__) {
|
|
47
|
-
console.log("[LoginForm] Rendering...");
|
|
48
|
-
}
|
|
49
|
-
|
|
50
40
|
return (
|
|
51
41
|
<>
|
|
52
42
|
<AtomicInput
|
|
53
|
-
label={
|
|
43
|
+
label={translations.email}
|
|
54
44
|
value={email}
|
|
55
45
|
onChangeText={handleEmailChange}
|
|
56
|
-
placeholder={
|
|
46
|
+
placeholder={translations.emailPlaceholder}
|
|
57
47
|
keyboardType="email-address"
|
|
58
48
|
autoCapitalize="none"
|
|
59
49
|
disabled={loading}
|
|
@@ -68,10 +58,10 @@ export const LoginForm: React.FC<LoginFormProps> = ({ onNavigateToRegister }) =>
|
|
|
68
58
|
|
|
69
59
|
<AtomicInput
|
|
70
60
|
ref={passwordRef}
|
|
71
|
-
label={
|
|
61
|
+
label={translations.password}
|
|
72
62
|
value={password}
|
|
73
63
|
onChangeText={handlePasswordChange}
|
|
74
|
-
placeholder={
|
|
64
|
+
placeholder={translations.passwordPlaceholder}
|
|
75
65
|
secureTextEntry
|
|
76
66
|
showPasswordToggle
|
|
77
67
|
autoCapitalize="none"
|
|
@@ -94,12 +84,12 @@ export const LoginForm: React.FC<LoginFormProps> = ({ onNavigateToRegister }) =>
|
|
|
94
84
|
fullWidth
|
|
95
85
|
style={styles.signInButton}
|
|
96
86
|
>
|
|
97
|
-
{
|
|
87
|
+
{translations.signIn}
|
|
98
88
|
</AtomicButton>
|
|
99
89
|
|
|
100
90
|
<AuthLink
|
|
101
|
-
text={
|
|
102
|
-
linkText={
|
|
91
|
+
text={translations.dontHaveAccount}
|
|
92
|
+
linkText={translations.createAccount}
|
|
103
93
|
onPress={onNavigateToRegister}
|
|
104
94
|
disabled={loading}
|
|
105
95
|
/>
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { View, StyleSheet } from "react-native";
|
|
3
3
|
import { useAppDesignTokens, AtomicText } from "@umituz/react-native-design-system";
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
export interface PasswordMatchTranslations {
|
|
6
|
+
match: string;
|
|
7
|
+
noMatch: string;
|
|
8
|
+
}
|
|
5
9
|
|
|
6
10
|
export interface PasswordMatchIndicatorProps {
|
|
11
|
+
translations: PasswordMatchTranslations;
|
|
7
12
|
isMatch: boolean;
|
|
8
13
|
}
|
|
9
14
|
|
|
10
15
|
export const PasswordMatchIndicator: React.FC<PasswordMatchIndicatorProps> = ({
|
|
16
|
+
translations,
|
|
11
17
|
isMatch,
|
|
12
18
|
}) => {
|
|
13
19
|
const tokens = useAppDesignTokens();
|
|
14
|
-
const { t } = useLocalization();
|
|
15
|
-
|
|
16
20
|
const color = isMatch ? tokens.colors.success : tokens.colors.error;
|
|
17
|
-
const text = isMatch
|
|
18
|
-
? t("auth.passwordsMatch")
|
|
19
|
-
: t("auth.passwordsDontMatch");
|
|
21
|
+
const text = isMatch ? translations.match : translations.noMatch;
|
|
20
22
|
|
|
21
23
|
return (
|
|
22
24
|
<View style={styles.container}>
|
|
@@ -39,4 +41,3 @@ const styles = StyleSheet.create({
|
|
|
39
41
|
borderRadius: 3,
|
|
40
42
|
},
|
|
41
43
|
});
|
|
42
|
-
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { View, StyleSheet } from "react-native";
|
|
3
3
|
import { useAppDesignTokens, AtomicText, type ColorVariant } from "@umituz/react-native-design-system";
|
|
4
|
-
import { useLocalization } from "@umituz/react-native-localization";
|
|
5
4
|
import type { PasswordRequirements } from "../../infrastructure/utils/AuthValidation";
|
|
6
5
|
|
|
6
|
+
export interface PasswordStrengthTranslations {
|
|
7
|
+
minLength: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
7
10
|
export interface PasswordStrengthIndicatorProps {
|
|
11
|
+
translations: PasswordStrengthTranslations;
|
|
8
12
|
requirements: PasswordRequirements;
|
|
9
13
|
showLabels?: boolean;
|
|
10
14
|
}
|
|
@@ -24,10 +28,6 @@ const RequirementDot: React.FC<RequirementDotProps> = ({
|
|
|
24
28
|
}) => {
|
|
25
29
|
const tokens = useAppDesignTokens();
|
|
26
30
|
const colorKey = isValid ? successColor : pendingColor;
|
|
27
|
-
|
|
28
|
-
// Resolve the color value from the token key for the View background
|
|
29
|
-
// We use type assertion since we know these are valid specific keys passed from parent
|
|
30
|
-
// but tokens.colors index signature might be limited
|
|
31
31
|
const dotColor = (tokens.colors as Record<string, string>)[colorKey] || tokens.colors.textTertiary;
|
|
32
32
|
|
|
33
33
|
return (
|
|
@@ -40,33 +40,30 @@ const RequirementDot: React.FC<RequirementDotProps> = ({
|
|
|
40
40
|
);
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
export const PasswordStrengthIndicator: React.FC<
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
export const PasswordStrengthIndicator: React.FC<PasswordStrengthIndicatorProps> = ({
|
|
44
|
+
translations,
|
|
45
|
+
requirements,
|
|
46
|
+
showLabels = true,
|
|
47
|
+
}) => {
|
|
46
48
|
const tokens = useAppDesignTokens();
|
|
47
|
-
const { t } = useLocalization();
|
|
48
|
-
|
|
49
49
|
const successColor: ColorVariant = "success";
|
|
50
50
|
const pendingColor: ColorVariant = "textTertiary";
|
|
51
51
|
|
|
52
52
|
const items = [
|
|
53
|
-
{ key: "minLength", label:
|
|
53
|
+
{ key: "minLength", label: translations.minLength, isValid: requirements.hasMinLength },
|
|
54
54
|
];
|
|
55
55
|
|
|
56
56
|
if (!showLabels) {
|
|
57
57
|
return (
|
|
58
58
|
<View style={styles.dotsOnly}>
|
|
59
59
|
{items.map((item) => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
const colorKey = item.isValid ? successColor : pendingColor;
|
|
61
|
+
const dotColor = (tokens.colors as Record<string, string>)[colorKey] || tokens.colors.textTertiary;
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
64
|
<View
|
|
65
65
|
key={item.key}
|
|
66
|
-
style={[
|
|
67
|
-
styles.dotOnly,
|
|
68
|
-
{ backgroundColor: dotColor },
|
|
69
|
-
]}
|
|
66
|
+
style={[styles.dotOnly, { backgroundColor: dotColor }]}
|
|
70
67
|
/>
|
|
71
68
|
);
|
|
72
69
|
})}
|
|
@@ -116,4 +113,3 @@ const styles = StyleSheet.create({
|
|
|
116
113
|
borderRadius: 4,
|
|
117
114
|
},
|
|
118
115
|
});
|
|
119
|
-
|
|
@@ -1,20 +1,33 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Register Form Component
|
|
3
|
-
* Single Responsibility: Render register form UI
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import React, { useRef } from "react";
|
|
7
2
|
import { StyleSheet, TextInput } from "react-native";
|
|
8
3
|
import { AtomicInput, AtomicButton } from "@umituz/react-native-design-system";
|
|
9
|
-
import { useLocalization } from "@umituz/react-native-localization";
|
|
10
4
|
import { useRegisterForm } from "../hooks/useRegisterForm";
|
|
11
5
|
import { AuthErrorDisplay } from "./AuthErrorDisplay";
|
|
12
6
|
import { AuthLink } from "./AuthLink";
|
|
13
|
-
import { AuthLegalLinks } from "./AuthLegalLinks";
|
|
14
|
-
import { PasswordStrengthIndicator } from "./PasswordStrengthIndicator";
|
|
15
|
-
import { PasswordMatchIndicator } from "./PasswordMatchIndicator";
|
|
7
|
+
import { AuthLegalLinks, type AuthLegalLinksTranslations } from "./AuthLegalLinks";
|
|
8
|
+
import { PasswordStrengthIndicator, type PasswordStrengthTranslations } from "./PasswordStrengthIndicator";
|
|
9
|
+
import { PasswordMatchIndicator, type PasswordMatchTranslations } from "./PasswordMatchIndicator";
|
|
10
|
+
|
|
11
|
+
export interface RegisterFormTranslations {
|
|
12
|
+
displayName: string;
|
|
13
|
+
displayNamePlaceholder: string;
|
|
14
|
+
email: string;
|
|
15
|
+
emailPlaceholder: string;
|
|
16
|
+
password: string;
|
|
17
|
+
passwordPlaceholder: string;
|
|
18
|
+
confirmPassword: string;
|
|
19
|
+
confirmPasswordPlaceholder: string;
|
|
20
|
+
signUp: string;
|
|
21
|
+
alreadyHaveAccount: string;
|
|
22
|
+
signIn: string;
|
|
23
|
+
bySigningUp: string;
|
|
24
|
+
legal: AuthLegalLinksTranslations;
|
|
25
|
+
passwordStrength: PasswordStrengthTranslations;
|
|
26
|
+
passwordMatch: PasswordMatchTranslations;
|
|
27
|
+
}
|
|
16
28
|
|
|
17
|
-
interface RegisterFormProps {
|
|
29
|
+
export interface RegisterFormProps {
|
|
30
|
+
translations: RegisterFormTranslations;
|
|
18
31
|
onNavigateToLogin: () => void;
|
|
19
32
|
termsUrl?: string;
|
|
20
33
|
privacyUrl?: string;
|
|
@@ -23,13 +36,13 @@ interface RegisterFormProps {
|
|
|
23
36
|
}
|
|
24
37
|
|
|
25
38
|
export const RegisterForm: React.FC<RegisterFormProps> = ({
|
|
39
|
+
translations,
|
|
26
40
|
onNavigateToLogin,
|
|
27
41
|
termsUrl,
|
|
28
42
|
privacyUrl,
|
|
29
43
|
onTermsPress,
|
|
30
44
|
onPrivacyPress,
|
|
31
45
|
}) => {
|
|
32
|
-
const { t } = useLocalization();
|
|
33
46
|
const emailRef = useRef<React.ElementRef<typeof TextInput>>(null);
|
|
34
47
|
const passwordRef = useRef<React.ElementRef<typeof TextInput>>(null);
|
|
35
48
|
const confirmPasswordRef = useRef<React.ElementRef<typeof TextInput>>(null);
|
|
@@ -54,12 +67,10 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
|
|
54
67
|
return (
|
|
55
68
|
<>
|
|
56
69
|
<AtomicInput
|
|
57
|
-
label={
|
|
70
|
+
label={translations.displayName}
|
|
58
71
|
value={displayName}
|
|
59
72
|
onChangeText={handleDisplayNameChange}
|
|
60
|
-
placeholder={
|
|
61
|
-
t("auth.displayNamePlaceholder")
|
|
62
|
-
}
|
|
73
|
+
placeholder={translations.displayNamePlaceholder}
|
|
63
74
|
autoCapitalize="words"
|
|
64
75
|
disabled={loading}
|
|
65
76
|
state={fieldErrors.displayName ? "error" : "default"}
|
|
@@ -72,10 +83,10 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
|
|
72
83
|
|
|
73
84
|
<AtomicInput
|
|
74
85
|
ref={emailRef}
|
|
75
|
-
label={
|
|
86
|
+
label={translations.email}
|
|
76
87
|
value={email}
|
|
77
88
|
onChangeText={handleEmailChange}
|
|
78
|
-
placeholder={
|
|
89
|
+
placeholder={translations.emailPlaceholder}
|
|
79
90
|
keyboardType="email-address"
|
|
80
91
|
autoCapitalize="none"
|
|
81
92
|
disabled={loading}
|
|
@@ -90,10 +101,10 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
|
|
90
101
|
|
|
91
102
|
<AtomicInput
|
|
92
103
|
ref={passwordRef}
|
|
93
|
-
label={
|
|
104
|
+
label={translations.password}
|
|
94
105
|
value={password}
|
|
95
106
|
onChangeText={handlePasswordChange}
|
|
96
|
-
placeholder={
|
|
107
|
+
placeholder={translations.passwordPlaceholder}
|
|
97
108
|
secureTextEntry
|
|
98
109
|
showPasswordToggle
|
|
99
110
|
autoCapitalize="none"
|
|
@@ -108,15 +119,18 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
|
|
108
119
|
style={styles.input}
|
|
109
120
|
/>
|
|
110
121
|
{password.length > 0 && (
|
|
111
|
-
<PasswordStrengthIndicator
|
|
122
|
+
<PasswordStrengthIndicator
|
|
123
|
+
translations={translations.passwordStrength}
|
|
124
|
+
requirements={passwordRequirements}
|
|
125
|
+
/>
|
|
112
126
|
)}
|
|
113
127
|
|
|
114
128
|
<AtomicInput
|
|
115
129
|
ref={confirmPasswordRef}
|
|
116
|
-
label={
|
|
130
|
+
label={translations.confirmPassword}
|
|
117
131
|
value={confirmPassword}
|
|
118
132
|
onChangeText={handleConfirmPasswordChange}
|
|
119
|
-
placeholder={
|
|
133
|
+
placeholder={translations.confirmPasswordPlaceholder}
|
|
120
134
|
secureTextEntry
|
|
121
135
|
showPasswordToggle
|
|
122
136
|
autoCapitalize="none"
|
|
@@ -130,7 +144,10 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
|
|
130
144
|
style={styles.input}
|
|
131
145
|
/>
|
|
132
146
|
{confirmPassword.length > 0 && (
|
|
133
|
-
<PasswordMatchIndicator
|
|
147
|
+
<PasswordMatchIndicator
|
|
148
|
+
translations={translations.passwordMatch}
|
|
149
|
+
isMatch={passwordsMatch}
|
|
150
|
+
/>
|
|
134
151
|
)}
|
|
135
152
|
|
|
136
153
|
<AuthErrorDisplay error={displayError} />
|
|
@@ -147,22 +164,23 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
|
|
|
147
164
|
fullWidth
|
|
148
165
|
style={styles.signUpButton}
|
|
149
166
|
>
|
|
150
|
-
{
|
|
167
|
+
{translations.signUp}
|
|
151
168
|
</AtomicButton>
|
|
152
169
|
|
|
153
170
|
<AuthLink
|
|
154
|
-
text={
|
|
155
|
-
linkText={
|
|
171
|
+
text={translations.alreadyHaveAccount}
|
|
172
|
+
linkText={translations.signIn}
|
|
156
173
|
onPress={onNavigateToLogin}
|
|
157
174
|
disabled={loading}
|
|
158
175
|
/>
|
|
159
176
|
|
|
160
177
|
<AuthLegalLinks
|
|
178
|
+
translations={translations.legal}
|
|
161
179
|
termsUrl={termsUrl}
|
|
162
180
|
privacyUrl={privacyUrl}
|
|
163
181
|
onTermsPress={onTermsPress}
|
|
164
182
|
onPrivacyPress={onPrivacyPress}
|
|
165
|
-
prefixText={
|
|
183
|
+
prefixText={translations.bySigningUp}
|
|
166
184
|
/>
|
|
167
185
|
</>
|
|
168
186
|
);
|
|
@@ -178,4 +196,3 @@ const styles = StyleSheet.create({
|
|
|
178
196
|
marginTop: 8,
|
|
179
197
|
},
|
|
180
198
|
});
|
|
181
|
-
|
|
@@ -1,76 +1,75 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
StyleSheet,
|
|
5
|
-
Platform,
|
|
6
|
-
} from "react-native";
|
|
7
|
-
import { Divider, AtomicButton } from "@umituz/react-native-design-system";
|
|
8
|
-
import { useLocalization } from "@umituz/react-native-localization";
|
|
2
|
+
import { View, StyleSheet } from "react-native";
|
|
3
|
+
import { AtomicText, AtomicButton, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
9
4
|
import type { SocialAuthProvider } from "../../domain/value-objects/AuthConfig";
|
|
10
5
|
|
|
6
|
+
export interface SocialLoginButtonsTranslations {
|
|
7
|
+
orContinueWith: string;
|
|
8
|
+
google: string;
|
|
9
|
+
apple: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
11
12
|
export interface SocialLoginButtonsProps {
|
|
12
|
-
|
|
13
|
+
translations: SocialLoginButtonsTranslations;
|
|
13
14
|
enabledProviders: SocialAuthProvider[];
|
|
14
|
-
/** Called when Google sign-in is pressed */
|
|
15
15
|
onGooglePress?: () => void;
|
|
16
|
-
/** Called when Apple sign-in is pressed */
|
|
17
16
|
onApplePress?: () => void;
|
|
18
|
-
/** Loading state for Google button */
|
|
19
17
|
googleLoading?: boolean;
|
|
20
|
-
/** Loading state for Apple button */
|
|
21
18
|
appleLoading?: boolean;
|
|
22
|
-
/** Disable all buttons */
|
|
23
|
-
disabled?: boolean;
|
|
24
19
|
}
|
|
25
20
|
|
|
26
21
|
export const SocialLoginButtons: React.FC<SocialLoginButtonsProps> = ({
|
|
22
|
+
translations,
|
|
27
23
|
enabledProviders,
|
|
28
24
|
onGooglePress,
|
|
29
25
|
onApplePress,
|
|
30
26
|
googleLoading = false,
|
|
31
27
|
appleLoading = false,
|
|
32
|
-
disabled = false,
|
|
33
28
|
}) => {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
const showGoogle = safeEnabledProviders.includes("google");
|
|
38
|
-
const showApple = safeEnabledProviders.includes("apple") && Platform.OS === "ios";
|
|
29
|
+
const tokens = useAppDesignTokens();
|
|
30
|
+
const hasGoogle = enabledProviders.includes("google");
|
|
31
|
+
const hasApple = enabledProviders.includes("apple");
|
|
39
32
|
|
|
40
|
-
if (!
|
|
33
|
+
if (!hasGoogle && !hasApple) {
|
|
41
34
|
return null;
|
|
42
35
|
}
|
|
43
36
|
|
|
44
37
|
return (
|
|
45
|
-
<View style={styles.container}>
|
|
46
|
-
<
|
|
38
|
+
<View style={[styles.container, { marginTop: tokens.spacing.lg }]}>
|
|
39
|
+
<View style={styles.dividerContainer}>
|
|
40
|
+
<View style={[styles.divider, { backgroundColor: tokens.colors.border }]} />
|
|
41
|
+
<AtomicText type="bodySmall" color="textSecondary" style={styles.dividerText}>
|
|
42
|
+
{translations.orContinueWith}
|
|
43
|
+
</AtomicText>
|
|
44
|
+
<View style={[styles.divider, { backgroundColor: tokens.colors.border }]} />
|
|
45
|
+
</View>
|
|
47
46
|
|
|
48
47
|
<View style={styles.buttonsContainer}>
|
|
49
|
-
{
|
|
48
|
+
{hasGoogle && onGooglePress && (
|
|
50
49
|
<AtomicButton
|
|
51
50
|
variant="outline"
|
|
52
|
-
onPress={onGooglePress
|
|
51
|
+
onPress={onGooglePress}
|
|
52
|
+
disabled={googleLoading || appleLoading}
|
|
53
53
|
loading={googleLoading}
|
|
54
|
-
disabled={disabled}
|
|
55
|
-
icon="logo-google"
|
|
56
54
|
fullWidth
|
|
57
55
|
style={styles.socialButton}
|
|
56
|
+
leftIcon="logo-google"
|
|
58
57
|
>
|
|
59
|
-
{
|
|
58
|
+
{translations.google}
|
|
60
59
|
</AtomicButton>
|
|
61
60
|
)}
|
|
62
61
|
|
|
63
|
-
{
|
|
62
|
+
{hasApple && onApplePress && (
|
|
64
63
|
<AtomicButton
|
|
65
64
|
variant="outline"
|
|
66
|
-
onPress={onApplePress
|
|
65
|
+
onPress={onApplePress}
|
|
66
|
+
disabled={googleLoading || appleLoading}
|
|
67
67
|
loading={appleLoading}
|
|
68
|
-
disabled={disabled}
|
|
69
|
-
icon="logo-apple"
|
|
70
68
|
fullWidth
|
|
71
69
|
style={styles.socialButton}
|
|
70
|
+
leftIcon="logo-apple"
|
|
72
71
|
>
|
|
73
|
-
{
|
|
72
|
+
{translations.apple}
|
|
74
73
|
</AtomicButton>
|
|
75
74
|
)}
|
|
76
75
|
</View>
|
|
@@ -79,16 +78,23 @@ export const SocialLoginButtons: React.FC<SocialLoginButtonsProps> = ({
|
|
|
79
78
|
};
|
|
80
79
|
|
|
81
80
|
const styles = StyleSheet.create({
|
|
82
|
-
container: {
|
|
83
|
-
|
|
81
|
+
container: {},
|
|
82
|
+
dividerContainer: {
|
|
83
|
+
flexDirection: "row",
|
|
84
|
+
alignItems: "center",
|
|
85
|
+
marginBottom: 16,
|
|
86
|
+
},
|
|
87
|
+
divider: {
|
|
88
|
+
flex: 1,
|
|
89
|
+
height: 1,
|
|
90
|
+
},
|
|
91
|
+
dividerText: {
|
|
92
|
+
marginHorizontal: 16,
|
|
84
93
|
},
|
|
85
94
|
buttonsContainer: {
|
|
86
|
-
flexDirection: 'row',
|
|
87
|
-
marginTop: 8,
|
|
88
95
|
gap: 12,
|
|
89
96
|
},
|
|
90
97
|
socialButton: {
|
|
91
|
-
|
|
98
|
+
minHeight: 48,
|
|
92
99
|
},
|
|
93
100
|
});
|
|
94
|
-
|