@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,15 +1,19 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useLoginForm Hook
|
|
3
|
-
* Single Responsibility: Handle login form logic
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import { useState, useCallback } from "react";
|
|
7
|
-
import { useLocalization } from "@umituz/react-native-localization";
|
|
8
2
|
import { useAuth } from "./useAuth";
|
|
9
3
|
import { getAuthErrorLocalizationKey } from "../utils/getAuthErrorMessage";
|
|
10
4
|
import { validateEmail, validatePasswordForLogin } from "../../infrastructure/utils/AuthValidation";
|
|
11
5
|
import { alertService } from "@umituz/react-native-design-system";
|
|
12
6
|
|
|
7
|
+
export interface LoginFormTranslations {
|
|
8
|
+
successTitle: string;
|
|
9
|
+
signInSuccess: string;
|
|
10
|
+
errors: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UseLoginFormConfig {
|
|
14
|
+
translations: LoginFormTranslations;
|
|
15
|
+
}
|
|
16
|
+
|
|
13
17
|
export interface UseLoginFormResult {
|
|
14
18
|
email: string;
|
|
15
19
|
password: string;
|
|
@@ -24,9 +28,9 @@ export interface UseLoginFormResult {
|
|
|
24
28
|
displayError: string | null;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
export function useLoginForm(): UseLoginFormResult {
|
|
28
|
-
const { t } = useLocalization();
|
|
31
|
+
export function useLoginForm(config?: UseLoginFormConfig): UseLoginFormResult {
|
|
29
32
|
const { signIn, loading, error, continueAnonymously } = useAuth();
|
|
33
|
+
const translations = config?.translations;
|
|
30
34
|
|
|
31
35
|
const [email, setEmail] = useState("");
|
|
32
36
|
const [password, setPassword] = useState("");
|
|
@@ -34,6 +38,10 @@ export function useLoginForm(): UseLoginFormResult {
|
|
|
34
38
|
const [passwordError, setPasswordError] = useState<string | null>(null);
|
|
35
39
|
const [localError, setLocalError] = useState<string | null>(null);
|
|
36
40
|
|
|
41
|
+
const getErrorMessage = useCallback((key: string) => {
|
|
42
|
+
return translations?.errors?.[key] || key;
|
|
43
|
+
}, [translations]);
|
|
44
|
+
|
|
37
45
|
const handleEmailChange = useCallback(
|
|
38
46
|
(text: string) => {
|
|
39
47
|
setEmail(text);
|
|
@@ -61,13 +69,13 @@ export function useLoginForm(): UseLoginFormResult {
|
|
|
61
69
|
|
|
62
70
|
const emailResult = validateEmail(email.trim());
|
|
63
71
|
if (!emailResult.isValid && emailResult.error) {
|
|
64
|
-
setEmailError(
|
|
72
|
+
setEmailError(getErrorMessage(emailResult.error));
|
|
65
73
|
hasError = true;
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
const passwordResult = validatePasswordForLogin(password);
|
|
69
77
|
if (!passwordResult.isValid && passwordResult.error) {
|
|
70
|
-
setPasswordError(
|
|
78
|
+
setPasswordError(getErrorMessage(passwordResult.error));
|
|
71
79
|
hasError = true;
|
|
72
80
|
}
|
|
73
81
|
|
|
@@ -75,23 +83,25 @@ export function useLoginForm(): UseLoginFormResult {
|
|
|
75
83
|
|
|
76
84
|
try {
|
|
77
85
|
await signIn(email.trim(), password);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
|
|
87
|
+
if (translations) {
|
|
88
|
+
alertService.success(
|
|
89
|
+
translations.successTitle,
|
|
90
|
+
translations.signInSuccess
|
|
91
|
+
);
|
|
92
|
+
}
|
|
83
93
|
} catch (err: unknown) {
|
|
84
94
|
const localizationKey = getAuthErrorLocalizationKey(err);
|
|
85
|
-
const errorMessage =
|
|
95
|
+
const errorMessage = getErrorMessage(localizationKey);
|
|
86
96
|
setLocalError(errorMessage);
|
|
87
97
|
}
|
|
88
|
-
}, [email, password,
|
|
98
|
+
}, [email, password, signIn, translations, getErrorMessage]);
|
|
89
99
|
|
|
90
100
|
const handleContinueAnonymously = useCallback(async () => {
|
|
91
101
|
try {
|
|
92
102
|
await continueAnonymously();
|
|
93
103
|
} catch {
|
|
94
|
-
// Silent fail
|
|
104
|
+
// Silent fail
|
|
95
105
|
}
|
|
96
106
|
}, [continueAnonymously]);
|
|
97
107
|
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useRegisterForm Hook
|
|
3
|
-
* Single Responsibility: Handle register form logic
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import { useState, useCallback, useMemo } from "react";
|
|
7
|
-
import { useLocalization } from "@umituz/react-native-localization";
|
|
8
2
|
import {
|
|
9
3
|
validateEmail,
|
|
10
4
|
validatePasswordForRegister,
|
|
@@ -16,6 +10,16 @@ import { getAuthErrorLocalizationKey } from "../utils/getAuthErrorMessage";
|
|
|
16
10
|
import type { PasswordRequirements } from "../../infrastructure/utils/AuthValidation";
|
|
17
11
|
import { alertService } from "@umituz/react-native-design-system";
|
|
18
12
|
|
|
13
|
+
export interface RegisterFormTranslations {
|
|
14
|
+
successTitle: string;
|
|
15
|
+
signUpSuccess: string;
|
|
16
|
+
errors: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UseRegisterFormConfig {
|
|
20
|
+
translations: RegisterFormTranslations;
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
export interface UseRegisterFormResult {
|
|
20
24
|
displayName: string;
|
|
21
25
|
email: string;
|
|
@@ -39,12 +43,9 @@ export interface UseRegisterFormResult {
|
|
|
39
43
|
displayError: string | null;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
* Hook for register form logic
|
|
44
|
-
*/
|
|
45
|
-
export function useRegisterForm(): UseRegisterFormResult {
|
|
46
|
-
const { t } = useLocalization();
|
|
46
|
+
export function useRegisterForm(config?: UseRegisterFormConfig): UseRegisterFormResult {
|
|
47
47
|
const { signUp, loading, error } = useAuth();
|
|
48
|
+
const translations = config?.translations;
|
|
48
49
|
|
|
49
50
|
const [displayName, setDisplayName] = useState("");
|
|
50
51
|
const [email, setEmail] = useState("");
|
|
@@ -58,11 +59,13 @@ export function useRegisterForm(): UseRegisterFormResult {
|
|
|
58
59
|
confirmPassword?: string;
|
|
59
60
|
}>({});
|
|
60
61
|
|
|
62
|
+
const getErrorMessage = useCallback((key: string) => {
|
|
63
|
+
return translations?.errors?.[key] || key;
|
|
64
|
+
}, [translations]);
|
|
65
|
+
|
|
61
66
|
const passwordRequirements = useMemo((): PasswordRequirements => {
|
|
62
67
|
if (!password) {
|
|
63
|
-
return {
|
|
64
|
-
hasMinLength: false,
|
|
65
|
-
};
|
|
68
|
+
return { hasMinLength: false };
|
|
66
69
|
}
|
|
67
70
|
const result = validatePasswordForRegister(password, DEFAULT_PASSWORD_CONFIG);
|
|
68
71
|
return result.requirements;
|
|
@@ -76,9 +79,7 @@ export function useRegisterForm(): UseRegisterFormResult {
|
|
|
76
79
|
setDisplayName(text);
|
|
77
80
|
setFieldErrors((prev) => {
|
|
78
81
|
const next = { ...prev };
|
|
79
|
-
if (next.displayName)
|
|
80
|
-
delete next.displayName;
|
|
81
|
-
}
|
|
82
|
+
if (next.displayName) delete next.displayName;
|
|
82
83
|
return next;
|
|
83
84
|
});
|
|
84
85
|
setLocalError(null);
|
|
@@ -88,9 +89,7 @@ export function useRegisterForm(): UseRegisterFormResult {
|
|
|
88
89
|
setEmail(text);
|
|
89
90
|
setFieldErrors((prev) => {
|
|
90
91
|
const next = { ...prev };
|
|
91
|
-
if (next.email)
|
|
92
|
-
delete next.email;
|
|
93
|
-
}
|
|
92
|
+
if (next.email) delete next.email;
|
|
94
93
|
return next;
|
|
95
94
|
});
|
|
96
95
|
setLocalError(null);
|
|
@@ -100,12 +99,8 @@ export function useRegisterForm(): UseRegisterFormResult {
|
|
|
100
99
|
setPassword(text);
|
|
101
100
|
setFieldErrors((prev) => {
|
|
102
101
|
const next = { ...prev };
|
|
103
|
-
if (next.password)
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
if (next.confirmPassword) {
|
|
107
|
-
delete next.confirmPassword;
|
|
108
|
-
}
|
|
102
|
+
if (next.password) delete next.password;
|
|
103
|
+
if (next.confirmPassword) delete next.confirmPassword;
|
|
109
104
|
return next;
|
|
110
105
|
});
|
|
111
106
|
setLocalError(null);
|
|
@@ -115,9 +110,7 @@ export function useRegisterForm(): UseRegisterFormResult {
|
|
|
115
110
|
setConfirmPassword(text);
|
|
116
111
|
setFieldErrors((prev) => {
|
|
117
112
|
const next = { ...prev };
|
|
118
|
-
if (next.confirmPassword)
|
|
119
|
-
delete next.confirmPassword;
|
|
120
|
-
}
|
|
113
|
+
if (next.confirmPassword) delete next.confirmPassword;
|
|
121
114
|
return next;
|
|
122
115
|
});
|
|
123
116
|
setLocalError(null);
|
|
@@ -129,39 +122,34 @@ export function useRegisterForm(): UseRegisterFormResult {
|
|
|
129
122
|
|
|
130
123
|
const emailResult = validateEmail(email.trim());
|
|
131
124
|
if (!emailResult.isValid && emailResult.error) {
|
|
132
|
-
setFieldErrors((prev) => ({ ...prev, email:
|
|
125
|
+
setFieldErrors((prev) => ({ ...prev, email: getErrorMessage(emailResult.error as string) }));
|
|
133
126
|
return;
|
|
134
127
|
}
|
|
135
128
|
|
|
136
129
|
const passwordResult = validatePasswordForRegister(password, DEFAULT_PASSWORD_CONFIG);
|
|
137
130
|
if (!passwordResult.isValid && passwordResult.error) {
|
|
138
|
-
setFieldErrors((prev) => ({ ...prev, password:
|
|
131
|
+
setFieldErrors((prev) => ({ ...prev, password: getErrorMessage(passwordResult.error as string) }));
|
|
139
132
|
return;
|
|
140
133
|
}
|
|
141
134
|
|
|
142
135
|
const confirmResult = validatePasswordConfirmation(password, confirmPassword);
|
|
143
136
|
if (!confirmResult.isValid && confirmResult.error) {
|
|
144
|
-
setFieldErrors((prev) => ({ ...prev, confirmPassword:
|
|
137
|
+
setFieldErrors((prev) => ({ ...prev, confirmPassword: getErrorMessage(confirmResult.error as string) }));
|
|
145
138
|
return;
|
|
146
139
|
}
|
|
147
140
|
|
|
148
141
|
try {
|
|
149
|
-
await signUp(
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
alertService.success(
|
|
156
|
-
t("auth.successTitle"),
|
|
157
|
-
t("auth.signUpSuccess")
|
|
158
|
-
);
|
|
142
|
+
await signUp(email.trim(), password, displayName.trim() || undefined);
|
|
143
|
+
|
|
144
|
+
if (translations) {
|
|
145
|
+
alertService.success(translations.successTitle, translations.signUpSuccess);
|
|
146
|
+
}
|
|
159
147
|
} catch (err: unknown) {
|
|
160
148
|
const localizationKey = getAuthErrorLocalizationKey(err);
|
|
161
|
-
const errorMessage =
|
|
149
|
+
const errorMessage = getErrorMessage(localizationKey);
|
|
162
150
|
setLocalError(errorMessage);
|
|
163
151
|
}
|
|
164
|
-
}, [displayName, email, password, confirmPassword, signUp,
|
|
152
|
+
}, [displayName, email, password, confirmPassword, signUp, translations, getErrorMessage]);
|
|
165
153
|
|
|
166
154
|
const displayError = localError || error;
|
|
167
155
|
|
|
@@ -183,5 +171,3 @@ export function useRegisterForm(): UseRegisterFormResult {
|
|
|
183
171
|
displayError,
|
|
184
172
|
};
|
|
185
173
|
}
|
|
186
|
-
|
|
187
|
-
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth Navigator
|
|
3
|
-
* Stack navigator for authentication screens (Login, Register)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import React, { useEffect, useState } from "react";
|
|
7
2
|
import {
|
|
8
3
|
StackNavigator,
|
|
@@ -12,8 +7,8 @@ import {
|
|
|
12
7
|
type StackNavigatorConfig,
|
|
13
8
|
type StackScreenProps,
|
|
14
9
|
} from "@umituz/react-native-design-system";
|
|
15
|
-
import { LoginScreen } from "../screens/LoginScreen";
|
|
16
|
-
import { RegisterScreen } from "../screens/RegisterScreen";
|
|
10
|
+
import { LoginScreen, type LoginScreenTranslations } from "../screens/LoginScreen";
|
|
11
|
+
import { RegisterScreen, type RegisterScreenTranslations } from "../screens/RegisterScreen";
|
|
17
12
|
|
|
18
13
|
export type AuthStackParamList = {
|
|
19
14
|
Login: undefined;
|
|
@@ -22,26 +17,21 @@ export type AuthStackParamList = {
|
|
|
22
17
|
|
|
23
18
|
const SHOW_REGISTER_KEY = "auth_show_register";
|
|
24
19
|
|
|
20
|
+
export interface AuthNavigatorTranslations {
|
|
21
|
+
login: LoginScreenTranslations;
|
|
22
|
+
register: RegisterScreenTranslations;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
25
|
export interface AuthNavigatorProps {
|
|
26
|
-
|
|
27
|
-
* Terms of Service URL
|
|
28
|
-
*/
|
|
26
|
+
translations: AuthNavigatorTranslations;
|
|
29
27
|
termsUrl?: string;
|
|
30
|
-
/**
|
|
31
|
-
* Privacy Policy URL
|
|
32
|
-
*/
|
|
33
28
|
privacyUrl?: string;
|
|
34
|
-
/**
|
|
35
|
-
* Callback when Terms of Service is pressed
|
|
36
|
-
*/
|
|
37
29
|
onTermsPress?: () => void;
|
|
38
|
-
/**
|
|
39
|
-
* Callback when Privacy Policy is pressed
|
|
40
|
-
*/
|
|
41
30
|
onPrivacyPress?: () => void;
|
|
42
31
|
}
|
|
43
32
|
|
|
44
33
|
export const AuthNavigator: React.FC<AuthNavigatorProps> = ({
|
|
34
|
+
translations,
|
|
45
35
|
termsUrl,
|
|
46
36
|
privacyUrl,
|
|
47
37
|
onTermsPress,
|
|
@@ -71,11 +61,21 @@ export const AuthNavigator: React.FC<AuthNavigatorProps> = ({
|
|
|
71
61
|
return null;
|
|
72
62
|
}
|
|
73
63
|
|
|
64
|
+
const LoginScreenWrapper = (
|
|
65
|
+
props: StackScreenProps<AuthStackParamList, "Login">
|
|
66
|
+
) => (
|
|
67
|
+
<LoginScreen
|
|
68
|
+
{...props}
|
|
69
|
+
translations={translations.login}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
|
|
74
73
|
const RegisterScreenWrapper = (
|
|
75
74
|
props: StackScreenProps<AuthStackParamList, "Register">
|
|
76
75
|
) => (
|
|
77
76
|
<RegisterScreen
|
|
78
77
|
{...props}
|
|
78
|
+
translations={translations.register}
|
|
79
79
|
termsUrl={termsUrl}
|
|
80
80
|
privacyUrl={privacyUrl}
|
|
81
81
|
onTermsPress={onTermsPress}
|
|
@@ -90,7 +90,7 @@ export const AuthNavigator: React.FC<AuthNavigatorProps> = ({
|
|
|
90
90
|
cardStyle: { backgroundColor: tokens.colors.backgroundPrimary },
|
|
91
91
|
},
|
|
92
92
|
screens: [
|
|
93
|
-
{ name: "Login", component:
|
|
93
|
+
{ name: "Login", component: LoginScreenWrapper },
|
|
94
94
|
{ name: "Register", component: RegisterScreenWrapper },
|
|
95
95
|
],
|
|
96
96
|
};
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Change Password Screen
|
|
3
|
-
* Screen for users to update their password
|
|
4
|
-
*
|
|
5
|
-
* Features:
|
|
6
|
-
* - Current password validation via re-authentication
|
|
7
|
-
* - New password validation (strength, match)
|
|
8
|
-
* - Secure error handling
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
1
|
import React, { useState } from "react";
|
|
12
2
|
import { View, StyleSheet, Alert } from "react-native";
|
|
13
3
|
import {
|
|
@@ -18,23 +8,49 @@ import {
|
|
|
18
8
|
AtomicText,
|
|
19
9
|
useAppDesignTokens,
|
|
20
10
|
} from "@umituz/react-native-design-system";
|
|
21
|
-
import { useLocalization } from "@umituz/react-native-localization";
|
|
22
11
|
import {
|
|
23
12
|
updateUserPassword,
|
|
24
13
|
reauthenticateWithPassword,
|
|
25
14
|
getCurrentUserFromGlobal,
|
|
26
15
|
} from "@umituz/react-native-firebase";
|
|
27
16
|
|
|
17
|
+
export interface ChangePasswordTranslations {
|
|
18
|
+
title: string;
|
|
19
|
+
description: string;
|
|
20
|
+
currentPassword: string;
|
|
21
|
+
enterCurrentPassword: string;
|
|
22
|
+
newPassword: string;
|
|
23
|
+
enterNewPassword: string;
|
|
24
|
+
confirmPassword: string;
|
|
25
|
+
enterConfirmPassword: string;
|
|
26
|
+
requirements: string;
|
|
27
|
+
minLength: string;
|
|
28
|
+
uppercase: string;
|
|
29
|
+
lowercase: string;
|
|
30
|
+
number: string;
|
|
31
|
+
specialChar: string;
|
|
32
|
+
passwordsMatch: string;
|
|
33
|
+
changePassword: string;
|
|
34
|
+
changing: string;
|
|
35
|
+
cancel: string;
|
|
36
|
+
success: string;
|
|
37
|
+
error: string;
|
|
38
|
+
fillAllFields: string;
|
|
39
|
+
unauthorized: string;
|
|
40
|
+
signInFailed: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
export interface ChangePasswordScreenProps {
|
|
44
|
+
translations: ChangePasswordTranslations;
|
|
29
45
|
onSuccess?: () => void;
|
|
30
46
|
onCancel?: () => void;
|
|
31
47
|
}
|
|
32
48
|
|
|
33
49
|
export const ChangePasswordScreen: React.FC<ChangePasswordScreenProps> = ({
|
|
50
|
+
translations,
|
|
34
51
|
onSuccess,
|
|
35
52
|
onCancel,
|
|
36
53
|
}) => {
|
|
37
|
-
const { t } = useLocalization();
|
|
38
54
|
const tokens = useAppDesignTokens();
|
|
39
55
|
|
|
40
56
|
const [currentPassword, setCurrentPassword] = useState("");
|
|
@@ -43,12 +59,6 @@ export const ChangePasswordScreen: React.FC<ChangePasswordScreenProps> = ({
|
|
|
43
59
|
const [loading, setLoading] = useState(false);
|
|
44
60
|
const [error, setError] = useState<string | null>(null);
|
|
45
61
|
|
|
46
|
-
/* Removed manual visibility states */
|
|
47
|
-
/* const [showCurrentPassword, setShowCurrentPassword] = useState(false); */
|
|
48
|
-
/* const [showNewPassword, setShowNewPassword] = useState(false); */
|
|
49
|
-
/* const [showConfirmPassword, setShowConfirmPassword] = useState(false); */
|
|
50
|
-
|
|
51
|
-
// Validation state
|
|
52
62
|
const isLengthValid = newPassword.length >= 8;
|
|
53
63
|
const hasUppercase = /[A-Z]/.test(newPassword);
|
|
54
64
|
const hasLowercase = /[a-z]/.test(newPassword);
|
|
@@ -67,13 +77,13 @@ export const ChangePasswordScreen: React.FC<ChangePasswordScreenProps> = ({
|
|
|
67
77
|
|
|
68
78
|
const handleChangePassword = async () => {
|
|
69
79
|
if (!isValid) {
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
Alert.alert("Error", translations.fillAllFields);
|
|
81
|
+
return;
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
const user = getCurrentUserFromGlobal();
|
|
75
85
|
if (!user) {
|
|
76
|
-
setError(
|
|
86
|
+
setError(translations.unauthorized);
|
|
77
87
|
return;
|
|
78
88
|
}
|
|
79
89
|
|
|
@@ -81,45 +91,29 @@ export const ChangePasswordScreen: React.FC<ChangePasswordScreenProps> = ({
|
|
|
81
91
|
setError(null);
|
|
82
92
|
|
|
83
93
|
try {
|
|
84
|
-
// 1. Re-authenticate
|
|
85
94
|
const reauthResult = await reauthenticateWithPassword(user, currentPassword);
|
|
86
95
|
if (!reauthResult.success) {
|
|
87
|
-
setError(reauthResult.error?.message ||
|
|
96
|
+
setError(reauthResult.error?.message || translations.signInFailed);
|
|
88
97
|
setLoading(false);
|
|
89
98
|
return;
|
|
90
99
|
}
|
|
91
100
|
|
|
92
|
-
// 2. Update Password
|
|
93
101
|
const updateResult = await updateUserPassword(user, newPassword);
|
|
94
102
|
if (updateResult.success) {
|
|
95
|
-
Alert.alert(
|
|
96
|
-
|
|
103
|
+
Alert.alert("Success", translations.success, [
|
|
104
|
+
{ text: "OK", onPress: onSuccess }
|
|
97
105
|
]);
|
|
98
106
|
} else {
|
|
99
|
-
setError(updateResult.error?.message ||
|
|
107
|
+
setError(updateResult.error?.message || translations.error);
|
|
100
108
|
}
|
|
101
109
|
} catch (e: unknown) {
|
|
102
|
-
const errorMessage = e instanceof Error ? e.message :
|
|
110
|
+
const errorMessage = e instanceof Error ? e.message : translations.error;
|
|
103
111
|
setError(errorMessage);
|
|
104
112
|
} finally {
|
|
105
113
|
setLoading(false);
|
|
106
114
|
}
|
|
107
115
|
};
|
|
108
116
|
|
|
109
|
-
const RequirementsList = () => (
|
|
110
|
-
<View style={[styles.requirementsContainer, { backgroundColor: tokens.colors.surfaceSecondary }]}>
|
|
111
|
-
<AtomicText type="labelMedium" style={{ color: tokens.colors.textSecondary, marginBottom: 8 }}>
|
|
112
|
-
{t("auth.passwordChange.requirements")}
|
|
113
|
-
</AtomicText>
|
|
114
|
-
<RequirementItem label={t("auth.passwordChange.minLength")} met={isLengthValid} />
|
|
115
|
-
<RequirementItem label={t("auth.passwordChange.uppercase")} met={hasUppercase} />
|
|
116
|
-
<RequirementItem label={t("auth.passwordChange.lowercase")} met={hasLowercase} />
|
|
117
|
-
<RequirementItem label={t("auth.passwordChange.number")} met={hasNumber} />
|
|
118
|
-
<RequirementItem label={t("auth.passwordChange.specialChar")} met={hasSpecialChar} />
|
|
119
|
-
<RequirementItem label={t("auth.passwordChange.passwordsMatch")} met={passwordsMatch} />
|
|
120
|
-
</View>
|
|
121
|
-
);
|
|
122
|
-
|
|
123
117
|
const RequirementItem = ({ label, met }: { label: string; met: boolean }) => (
|
|
124
118
|
<View style={styles.requirementItem}>
|
|
125
119
|
<View
|
|
@@ -140,20 +134,19 @@ export const ChangePasswordScreen: React.FC<ChangePasswordScreenProps> = ({
|
|
|
140
134
|
return (
|
|
141
135
|
<ScreenLayout
|
|
142
136
|
scrollable
|
|
143
|
-
header={<ScreenHeader title={
|
|
137
|
+
header={<ScreenHeader title={translations.title} />}
|
|
144
138
|
backgroundColor={tokens.colors.backgroundPrimary}
|
|
145
139
|
edges={["bottom"]}
|
|
146
140
|
>
|
|
147
141
|
<View style={styles.content}>
|
|
148
142
|
<AtomicText type="bodyMedium" style={{ color: tokens.colors.textSecondary, marginBottom: 24 }}>
|
|
149
|
-
{
|
|
143
|
+
{translations.description}
|
|
150
144
|
</AtomicText>
|
|
151
145
|
|
|
152
146
|
<View style={styles.form}>
|
|
153
|
-
{/* Current Password */}
|
|
154
147
|
<AtomicInput
|
|
155
|
-
label={
|
|
156
|
-
placeholder={
|
|
148
|
+
label={translations.currentPassword}
|
|
149
|
+
placeholder={translations.enterCurrentPassword}
|
|
157
150
|
value={currentPassword}
|
|
158
151
|
onChangeText={setCurrentPassword}
|
|
159
152
|
secureTextEntry
|
|
@@ -161,10 +154,9 @@ export const ChangePasswordScreen: React.FC<ChangePasswordScreenProps> = ({
|
|
|
161
154
|
variant="filled"
|
|
162
155
|
/>
|
|
163
156
|
|
|
164
|
-
{/* New Password */}
|
|
165
157
|
<AtomicInput
|
|
166
|
-
label={
|
|
167
|
-
placeholder={
|
|
158
|
+
label={translations.newPassword}
|
|
159
|
+
placeholder={translations.enterNewPassword}
|
|
168
160
|
value={newPassword}
|
|
169
161
|
onChangeText={setNewPassword}
|
|
170
162
|
secureTextEntry
|
|
@@ -172,10 +164,9 @@ export const ChangePasswordScreen: React.FC<ChangePasswordScreenProps> = ({
|
|
|
172
164
|
variant="filled"
|
|
173
165
|
/>
|
|
174
166
|
|
|
175
|
-
{/* Confirm Password */}
|
|
176
167
|
<AtomicInput
|
|
177
|
-
label={
|
|
178
|
-
placeholder={
|
|
168
|
+
label={translations.confirmPassword}
|
|
169
|
+
placeholder={translations.enterConfirmPassword}
|
|
179
170
|
value={confirmPassword}
|
|
180
171
|
onChangeText={setConfirmPassword}
|
|
181
172
|
secureTextEntry
|
|
@@ -183,7 +174,17 @@ export const ChangePasswordScreen: React.FC<ChangePasswordScreenProps> = ({
|
|
|
183
174
|
variant="filled"
|
|
184
175
|
/>
|
|
185
176
|
|
|
186
|
-
<
|
|
177
|
+
<View style={[styles.requirementsContainer, { backgroundColor: tokens.colors.surfaceSecondary }]}>
|
|
178
|
+
<AtomicText type="labelMedium" style={{ color: tokens.colors.textSecondary, marginBottom: 8 }}>
|
|
179
|
+
{translations.requirements}
|
|
180
|
+
</AtomicText>
|
|
181
|
+
<RequirementItem label={translations.minLength} met={isLengthValid} />
|
|
182
|
+
<RequirementItem label={translations.uppercase} met={hasUppercase} />
|
|
183
|
+
<RequirementItem label={translations.lowercase} met={hasLowercase} />
|
|
184
|
+
<RequirementItem label={translations.number} met={hasNumber} />
|
|
185
|
+
<RequirementItem label={translations.specialChar} met={hasSpecialChar} />
|
|
186
|
+
<RequirementItem label={translations.passwordsMatch} met={passwordsMatch} />
|
|
187
|
+
</View>
|
|
187
188
|
|
|
188
189
|
{error && (
|
|
189
190
|
<AtomicText style={{ color: tokens.colors.error, marginTop: 16 }}>
|
|
@@ -192,14 +193,14 @@ export const ChangePasswordScreen: React.FC<ChangePasswordScreenProps> = ({
|
|
|
192
193
|
)}
|
|
193
194
|
|
|
194
195
|
<View style={styles.actions}>
|
|
195
|
-
|
|
196
|
-
title={
|
|
196
|
+
<AtomicButton
|
|
197
|
+
title={translations.cancel}
|
|
197
198
|
onPress={onCancel || (() => {})}
|
|
198
199
|
variant="outline"
|
|
199
200
|
style={{ flex: 1 }}
|
|
200
201
|
/>
|
|
201
202
|
<AtomicButton
|
|
202
|
-
title={loading ?
|
|
203
|
+
title={loading ? translations.changing : translations.changePassword}
|
|
203
204
|
onPress={() => { void handleChangePassword(); }}
|
|
204
205
|
loading={loading}
|
|
205
206
|
disabled={!isValid || loading}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Login Screen
|
|
3
|
-
* Beautiful, production-ready login screen with email/password and guest mode
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import React from "react";
|
|
7
2
|
import { useAppNavigation, AtomicCard, ScreenLayout, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
8
|
-
import { useLocalization } from "@umituz/react-native-localization";
|
|
9
3
|
import { AuthHeader } from "../components/AuthHeader";
|
|
10
|
-
import { LoginForm } from "../components/LoginForm";
|
|
4
|
+
import { LoginForm, type LoginFormTranslations } from "../components/LoginForm";
|
|
5
|
+
|
|
6
|
+
export interface LoginScreenTranslations {
|
|
7
|
+
title: string;
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
form: LoginFormTranslations;
|
|
10
|
+
}
|
|
11
11
|
|
|
12
|
-
export
|
|
13
|
-
|
|
12
|
+
export interface LoginScreenProps {
|
|
13
|
+
translations: LoginScreenTranslations;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const LoginScreen: React.FC<LoginScreenProps> = ({ translations }) => {
|
|
14
17
|
const navigation = useAppNavigation();
|
|
15
18
|
const tokens = useAppDesignTokens();
|
|
16
19
|
|
|
@@ -26,11 +29,13 @@ export const LoginScreen: React.FC = () => {
|
|
|
26
29
|
contentContainerStyle={{ justifyContent: "center" }}
|
|
27
30
|
backgroundColor={tokens.colors.backgroundPrimary}
|
|
28
31
|
>
|
|
29
|
-
<AuthHeader title={
|
|
32
|
+
<AuthHeader title={translations.title} subtitle={translations.subtitle} />
|
|
30
33
|
<AtomicCard variant="elevated" padding="lg">
|
|
31
|
-
<LoginForm
|
|
34
|
+
<LoginForm
|
|
35
|
+
translations={translations.form}
|
|
36
|
+
onNavigateToRegister={handleNavigateToRegister}
|
|
37
|
+
/>
|
|
32
38
|
</AtomicCard>
|
|
33
39
|
</ScreenLayout>
|
|
34
40
|
);
|
|
35
41
|
};
|
|
36
|
-
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Register Screen
|
|
3
|
-
* Beautiful, production-ready registration screen with validation
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
import React from "react";
|
|
7
2
|
import { useAppNavigation, AtomicCard, ScreenLayout, useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
8
|
-
import { useLocalization } from "@umituz/react-native-localization";
|
|
9
3
|
import { AuthHeader } from "../components/AuthHeader";
|
|
10
|
-
import { RegisterForm } from "../components/RegisterForm";
|
|
4
|
+
import { RegisterForm, type RegisterFormTranslations } from "../components/RegisterForm";
|
|
5
|
+
|
|
6
|
+
export interface RegisterScreenTranslations {
|
|
7
|
+
title: string;
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
form: RegisterFormTranslations;
|
|
10
|
+
}
|
|
11
11
|
|
|
12
12
|
export interface RegisterScreenProps {
|
|
13
|
+
translations: RegisterScreenTranslations;
|
|
13
14
|
termsUrl?: string;
|
|
14
15
|
privacyUrl?: string;
|
|
15
16
|
onTermsPress?: () => void;
|
|
@@ -17,12 +18,12 @@ export interface RegisterScreenProps {
|
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
export const RegisterScreen: React.FC<RegisterScreenProps> = ({
|
|
21
|
+
translations,
|
|
20
22
|
termsUrl,
|
|
21
23
|
privacyUrl,
|
|
22
24
|
onTermsPress,
|
|
23
25
|
onPrivacyPress,
|
|
24
26
|
}) => {
|
|
25
|
-
const { t } = useLocalization();
|
|
26
27
|
const navigation = useAppNavigation();
|
|
27
28
|
const tokens = useAppDesignTokens();
|
|
28
29
|
|
|
@@ -32,15 +33,16 @@ export const RegisterScreen: React.FC<RegisterScreenProps> = ({
|
|
|
32
33
|
|
|
33
34
|
return (
|
|
34
35
|
<ScreenLayout
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
scrollable
|
|
37
|
+
keyboardAvoiding
|
|
38
|
+
maxWidth={440}
|
|
39
|
+
contentContainerStyle={{ justifyContent: "center" }}
|
|
40
|
+
backgroundColor={tokens.colors.backgroundPrimary}
|
|
40
41
|
>
|
|
41
|
-
<AuthHeader title={
|
|
42
|
+
<AuthHeader title={translations.title} subtitle={translations.subtitle} />
|
|
42
43
|
<AtomicCard variant="elevated" padding="lg">
|
|
43
44
|
<RegisterForm
|
|
45
|
+
translations={translations.form}
|
|
44
46
|
onNavigateToLogin={handleNavigateToLogin}
|
|
45
47
|
termsUrl={termsUrl}
|
|
46
48
|
privacyUrl={privacyUrl}
|
|
@@ -51,4 +53,3 @@ export const RegisterScreen: React.FC<RegisterScreenProps> = ({
|
|
|
51
53
|
</ScreenLayout>
|
|
52
54
|
);
|
|
53
55
|
};
|
|
54
|
-
|