@umituz/react-native-auth 3.6.61 → 3.6.63
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/application/ports/IAuthRepository.ts +3 -3
- package/src/index.ts +5 -18
- package/src/infrastructure/providers/FirebaseAuthProvider.ts +6 -2
- package/src/infrastructure/repositories/AuthRepository.ts +3 -3
- package/src/infrastructure/services/AuthEventService.ts +4 -0
- package/src/infrastructure/services/AuthService.ts +14 -36
- package/src/infrastructure/services/initializeAuth.ts +0 -15
- package/src/infrastructure/utils/AuthValidation.ts +5 -2
- package/src/infrastructure/utils/UserMapper.ts +9 -6
- package/src/infrastructure/utils/authStateHandler.ts +0 -7
- package/src/infrastructure/utils/validation/sanitization.ts +2 -89
- package/src/init/createAuthInitModule.ts +0 -15
- package/src/presentation/hooks/mutations/useAuthMutations.ts +3 -3
- package/src/presentation/hooks/useAuthBottomSheet.ts +10 -1
- package/src/presentation/hooks/useGoogleAuth.ts +43 -11
- package/src/presentation/stores/auth.selectors.ts +12 -6
- package/src/presentation/stores/authStore.ts +6 -9
- package/src/presentation/stores/initializeAuthListener.ts +44 -7
- package/src/application/ports/IAuthService.ts +0 -60
- package/src/domain/utils/migration.ts +0 -44
- package/src/infrastructure/adapters/UIProviderAdapter.ts +0 -43
- package/src/infrastructure/services/UserDocument.types.ts +0 -60
- package/src/infrastructure/services/UserDocumentService.ts +0 -86
- package/src/infrastructure/services/app-service-helpers.ts +0 -35
- package/src/infrastructure/types/UI.types.ts +0 -11
- package/src/infrastructure/utils/auth-tracker.util.ts +0 -23
- package/src/infrastructure/utils/userDocumentBuilder.util.ts +0 -114
- package/src/infrastructure/utils/validation/BaseValidators.ts +0 -35
- package/src/infrastructure/utils/validation/CollectionValidators.ts +0 -56
- package/src/infrastructure/utils/validation/DateValidators.ts +0 -71
- package/src/infrastructure/utils/validation/FormValidators.ts +0 -22
- package/src/infrastructure/utils/validation/NumberValidators.ts +0 -50
- package/src/infrastructure/utils/validation/StringValidators.ts +0 -55
- package/src/infrastructure/utils/validation/types.ts +0 -15
- package/src/presentation/screens/change-password/ChangePasswordScreen.tsx +0 -179
- package/src/presentation/screens/change-password/ChangePasswordScreen.types.ts +0 -77
- package/src/presentation/screens/change-password/RequirementItem.tsx +0 -47
- package/src/presentation/screens/change-password/index.ts +0 -6
- package/src/types/translations.types.ts +0 -89
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User Document Builder Utility
|
|
3
|
-
* Builds user document data for Firestore
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { serverTimestamp } from "firebase/firestore";
|
|
7
|
-
import type {
|
|
8
|
-
UserDocumentUser,
|
|
9
|
-
UserDocumentExtras,
|
|
10
|
-
} from "../../infrastructure/services/UserDocument.types";
|
|
11
|
-
import { extractProvider } from "./UserMapper";
|
|
12
|
-
import type { AuthProviderType } from "../../domain/entities/AuthUser";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Map AuthProviderType to sign-up method string
|
|
16
|
-
*/
|
|
17
|
-
const PROVIDER_TO_SIGNUP_METHOD: Record<string, string> = {
|
|
18
|
-
"google.com": "google",
|
|
19
|
-
"apple.com": "apple",
|
|
20
|
-
"password": "email",
|
|
21
|
-
"anonymous": "anonymous",
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Gets the sign-up method from user provider data
|
|
26
|
-
* Uses extractProvider from UserMapper as single source of truth for provider detection
|
|
27
|
-
*/
|
|
28
|
-
export function getSignUpMethod(user: UserDocumentUser): string | undefined {
|
|
29
|
-
const provider: AuthProviderType = extractProvider(user as Parameters<typeof extractProvider>[0]);
|
|
30
|
-
if (provider === "unknown") return user.email ? "email" : undefined;
|
|
31
|
-
return PROVIDER_TO_SIGNUP_METHOD[provider];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Builds base user data from user object and extras
|
|
36
|
-
*/
|
|
37
|
-
export function buildBaseData(
|
|
38
|
-
user: UserDocumentUser,
|
|
39
|
-
extras?: UserDocumentExtras,
|
|
40
|
-
): Record<string, unknown> {
|
|
41
|
-
const data: Record<string, unknown> = {
|
|
42
|
-
displayName: user.displayName,
|
|
43
|
-
email: user.email,
|
|
44
|
-
photoURL: user.photoURL,
|
|
45
|
-
isAnonymous: user.isAnonymous,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
if (extras) {
|
|
49
|
-
const internalFields = ["signUpMethod", "previousAnonymousUserId"];
|
|
50
|
-
Object.keys(extras).forEach((key) => {
|
|
51
|
-
if (!internalFields.includes(key)) {
|
|
52
|
-
const val = extras[key];
|
|
53
|
-
if (val !== undefined) {
|
|
54
|
-
data[key] = val;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return data;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Apply anonymous-to-authenticated conversion fields to document data
|
|
65
|
-
*/
|
|
66
|
-
function applyConversionFields(data: Record<string, unknown>, extras?: UserDocumentExtras): void {
|
|
67
|
-
if (extras?.previousAnonymousUserId) {
|
|
68
|
-
data.previousAnonymousUserId = extras.previousAnonymousUserId;
|
|
69
|
-
data.convertedFromAnonymous = true;
|
|
70
|
-
data.convertedAt = serverTimestamp();
|
|
71
|
-
}
|
|
72
|
-
if (extras?.signUpMethod) {
|
|
73
|
-
data.signUpMethod = extras.signUpMethod;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Builds user document data for creation
|
|
79
|
-
*/
|
|
80
|
-
export function buildCreateData(
|
|
81
|
-
baseData: Record<string, unknown>,
|
|
82
|
-
extraFields: Record<string, unknown> | undefined,
|
|
83
|
-
extras?: UserDocumentExtras,
|
|
84
|
-
): Record<string, unknown> {
|
|
85
|
-
const createData: Record<string, unknown> = {
|
|
86
|
-
...baseData,
|
|
87
|
-
...extraFields,
|
|
88
|
-
createdAt: serverTimestamp(),
|
|
89
|
-
updatedAt: serverTimestamp(),
|
|
90
|
-
lastLoginAt: serverTimestamp(),
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
applyConversionFields(createData, extras);
|
|
94
|
-
|
|
95
|
-
return createData;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Builds user document data for update
|
|
100
|
-
*/
|
|
101
|
-
export function buildUpdateData(
|
|
102
|
-
baseData: Record<string, unknown>,
|
|
103
|
-
extras?: UserDocumentExtras,
|
|
104
|
-
): Record<string, unknown> {
|
|
105
|
-
const updateData: Record<string, unknown> = {
|
|
106
|
-
...baseData,
|
|
107
|
-
lastLoginAt: serverTimestamp(),
|
|
108
|
-
updatedAt: serverTimestamp(),
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
applyConversionFields(updateData, extras);
|
|
112
|
-
|
|
113
|
-
return updateData;
|
|
114
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { ValidationResult } from './types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Validate required field (not null, undefined, or empty string)
|
|
5
|
-
*/
|
|
6
|
-
export const validateRequired = (
|
|
7
|
-
value: unknown,
|
|
8
|
-
fieldName: string = "Field",
|
|
9
|
-
errorKey?: string
|
|
10
|
-
): ValidationResult => {
|
|
11
|
-
const isValid = value !== undefined && value !== null && (typeof value === 'string' ? value.trim() !== "" : true);
|
|
12
|
-
|
|
13
|
-
return {
|
|
14
|
-
isValid,
|
|
15
|
-
error: isValid ? undefined : (errorKey || `${fieldName} is required`)
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Validate pattern (regex)
|
|
21
|
-
*/
|
|
22
|
-
export const validatePattern = (
|
|
23
|
-
value: string,
|
|
24
|
-
pattern: RegExp,
|
|
25
|
-
fieldName: string = "Field",
|
|
26
|
-
errorKey?: string
|
|
27
|
-
): ValidationResult => {
|
|
28
|
-
if (!value) return { isValid: true }; // Use validateRequired for mandatory check
|
|
29
|
-
|
|
30
|
-
const isValid = pattern.test(value);
|
|
31
|
-
return {
|
|
32
|
-
isValid,
|
|
33
|
-
error: isValid ? undefined : (errorKey || `${fieldName} format is invalid`)
|
|
34
|
-
};
|
|
35
|
-
};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { ValidationResult } from './types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Custom enum validation
|
|
5
|
-
* Generic validator for enum-like values with custom options
|
|
6
|
-
*/
|
|
7
|
-
export const validateEnum = (
|
|
8
|
-
value: string,
|
|
9
|
-
validOptions: readonly string[],
|
|
10
|
-
fieldName: string = "Field",
|
|
11
|
-
errorKey?: string
|
|
12
|
-
): ValidationResult => {
|
|
13
|
-
if (!value || value.trim() === "") {
|
|
14
|
-
return { isValid: false, error: `${fieldName} is required` };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const isValid = validOptions.includes(value.toLowerCase());
|
|
18
|
-
return {
|
|
19
|
-
isValid,
|
|
20
|
-
error: isValid ? undefined : (errorKey || `Please select a valid ${fieldName.toLowerCase()}`)
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Tags validation
|
|
26
|
-
* Generic validator for array of strings with constraints
|
|
27
|
-
*/
|
|
28
|
-
export const validateTags = (
|
|
29
|
-
tags: string[],
|
|
30
|
-
maxTags: number = 10,
|
|
31
|
-
maxTagLength: number = 20,
|
|
32
|
-
errorKey?: string
|
|
33
|
-
): ValidationResult => {
|
|
34
|
-
if (!Array.isArray(tags)) {
|
|
35
|
-
return { isValid: false, error: errorKey || "Tags must be an array" };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (tags.length > maxTags) {
|
|
39
|
-
return { isValid: false, error: errorKey || `Maximum ${maxTags} tags allowed` };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
for (const tag of tags) {
|
|
43
|
-
if (typeof tag !== "string" || tag.trim().length === 0) {
|
|
44
|
-
return { isValid: false, error: errorKey || "All tags must be non-empty strings" };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (tag.trim().length > maxTagLength) {
|
|
48
|
-
return {
|
|
49
|
-
isValid: false,
|
|
50
|
-
error: errorKey || `Each tag must be at most ${maxTagLength} characters`,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return { isValid: true };
|
|
56
|
-
};
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { ValidationResult } from './types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Calculate age from a birth date
|
|
5
|
-
*/
|
|
6
|
-
export function calculateAge(birthDate: Date): number {
|
|
7
|
-
const today = new Date();
|
|
8
|
-
let age = today.getFullYear() - birthDate.getFullYear();
|
|
9
|
-
const m = today.getMonth() - birthDate.getMonth();
|
|
10
|
-
if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
|
|
11
|
-
age--;
|
|
12
|
-
}
|
|
13
|
-
return age;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Validate date of birth
|
|
18
|
-
*/
|
|
19
|
-
export const validateDateOfBirth = (
|
|
20
|
-
date: Date,
|
|
21
|
-
minAge: number = 13,
|
|
22
|
-
errorKey?: string
|
|
23
|
-
): ValidationResult => {
|
|
24
|
-
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
|
|
25
|
-
return { isValid: false, error: "Please enter a valid date" };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const age = calculateAge(date);
|
|
29
|
-
|
|
30
|
-
if (age < minAge) {
|
|
31
|
-
return { isValid: false, error: errorKey || `You must be at least ${minAge} years old` };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (age > 120) {
|
|
35
|
-
return { isValid: false, error: "Please enter a valid date of birth" };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return { isValid: true };
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Date range validation
|
|
43
|
-
* Validates date is within a specific range
|
|
44
|
-
*/
|
|
45
|
-
export const validateDateRange = (
|
|
46
|
-
date: Date,
|
|
47
|
-
minDate?: Date,
|
|
48
|
-
maxDate?: Date,
|
|
49
|
-
fieldName: string = "Date",
|
|
50
|
-
errorKey?: string
|
|
51
|
-
): ValidationResult => {
|
|
52
|
-
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
|
|
53
|
-
return { isValid: false, error: errorKey || `Please enter a valid ${fieldName.toLowerCase()}` };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (minDate && date < minDate) {
|
|
57
|
-
return {
|
|
58
|
-
isValid: false,
|
|
59
|
-
error: errorKey || `${fieldName} cannot be before ${minDate.toLocaleDateString()}`,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (maxDate && date > maxDate) {
|
|
64
|
-
return {
|
|
65
|
-
isValid: false,
|
|
66
|
-
error: errorKey || `${fieldName} cannot be after ${maxDate.toLocaleDateString()}`,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return { isValid: true };
|
|
71
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { ValidationResult } from './types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Batch validation helper
|
|
5
|
-
* Validates multiple fields and returns all errors in a record
|
|
6
|
-
*/
|
|
7
|
-
export const batchValidate = (
|
|
8
|
-
validations: Array<{ field: string; validator: () => ValidationResult }>,
|
|
9
|
-
): { isValid: boolean; errors: Record<string, string> } => {
|
|
10
|
-
const errors: Record<string, string> = {};
|
|
11
|
-
let isValid = true;
|
|
12
|
-
|
|
13
|
-
validations.forEach(({ field, validator }) => {
|
|
14
|
-
const result = validator();
|
|
15
|
-
if (!result.isValid && result.error) {
|
|
16
|
-
errors[field] = result.error;
|
|
17
|
-
isValid = false;
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
return { isValid, errors };
|
|
22
|
-
};
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { ValidationResult } from './types';
|
|
2
|
-
import { calculateAge } from './DateValidators';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Validate numeric range
|
|
6
|
-
*/
|
|
7
|
-
export const validateNumberRange = (
|
|
8
|
-
value: number,
|
|
9
|
-
min: number,
|
|
10
|
-
max: number,
|
|
11
|
-
fieldName: string = "Number",
|
|
12
|
-
errorKey?: string
|
|
13
|
-
): ValidationResult => {
|
|
14
|
-
const isValid = value >= min && value <= max;
|
|
15
|
-
return {
|
|
16
|
-
isValid,
|
|
17
|
-
error: isValid ? undefined : (errorKey || `${fieldName} must be between ${min} and ${max}`)
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Validate positive number
|
|
23
|
-
*/
|
|
24
|
-
export const validatePositiveNumber = (
|
|
25
|
-
value: number,
|
|
26
|
-
fieldName: string = "Number",
|
|
27
|
-
errorKey?: string
|
|
28
|
-
): ValidationResult => {
|
|
29
|
-
const isValid = value > 0;
|
|
30
|
-
return {
|
|
31
|
-
isValid,
|
|
32
|
-
error: isValid ? undefined : (errorKey || `${fieldName} must be a positive number`)
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Validate age based on birth date
|
|
38
|
-
*/
|
|
39
|
-
export const validateAge = (
|
|
40
|
-
birthDate: Date,
|
|
41
|
-
minAge: number,
|
|
42
|
-
errorKey?: string
|
|
43
|
-
): ValidationResult => {
|
|
44
|
-
const age = calculateAge(birthDate);
|
|
45
|
-
const isValid = age >= minAge;
|
|
46
|
-
return {
|
|
47
|
-
isValid,
|
|
48
|
-
error: isValid ? undefined : (errorKey || `You must be at least ${minAge} years old`)
|
|
49
|
-
};
|
|
50
|
-
};
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { ValidationResult } from './types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Validate min length of a string
|
|
5
|
-
*/
|
|
6
|
-
export const validateMinLength = (
|
|
7
|
-
value: string,
|
|
8
|
-
minLength: number,
|
|
9
|
-
fieldName: string = "Field",
|
|
10
|
-
errorKey?: string
|
|
11
|
-
): ValidationResult => {
|
|
12
|
-
if (!value) return { isValid: false, error: `${fieldName} is required` };
|
|
13
|
-
|
|
14
|
-
const isValid = value.trim().length >= minLength;
|
|
15
|
-
return {
|
|
16
|
-
isValid,
|
|
17
|
-
error: isValid ? undefined : (errorKey || `${fieldName} must be at least ${minLength} characters`)
|
|
18
|
-
};
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Validate max length of a string
|
|
23
|
-
*/
|
|
24
|
-
export const validateMaxLength = (
|
|
25
|
-
value: string,
|
|
26
|
-
maxLength: number,
|
|
27
|
-
fieldName: string = "Field",
|
|
28
|
-
errorKey?: string
|
|
29
|
-
): ValidationResult => {
|
|
30
|
-
if (!value) return { isValid: true };
|
|
31
|
-
|
|
32
|
-
const isValid = value.trim().length <= maxLength;
|
|
33
|
-
return {
|
|
34
|
-
isValid,
|
|
35
|
-
error: isValid ? undefined : (errorKey || `${fieldName} must be at most ${maxLength} characters`)
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Validate phone number (E.164 format)
|
|
41
|
-
*/
|
|
42
|
-
export const validatePhone = (
|
|
43
|
-
phone: string,
|
|
44
|
-
errorKey?: string
|
|
45
|
-
): ValidationResult => {
|
|
46
|
-
if (!phone) return { isValid: true };
|
|
47
|
-
|
|
48
|
-
const phoneRegex = /^\+[1-9]\d{1,14}$/;
|
|
49
|
-
const isValid = phoneRegex.test(phone);
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
isValid,
|
|
53
|
-
error: isValid ? undefined : (errorKey || "Please enter a valid phone number")
|
|
54
|
-
};
|
|
55
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validation Result Interface
|
|
3
|
-
* Represents the outcome of a validation operation
|
|
4
|
-
*/
|
|
5
|
-
export interface ValidationResult {
|
|
6
|
-
/**
|
|
7
|
-
* Whether the validation passed
|
|
8
|
-
*/
|
|
9
|
-
isValid: boolean;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Error message or translation key if validation failed
|
|
13
|
-
*/
|
|
14
|
-
error?: string;
|
|
15
|
-
}
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Change Password Screen
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import React, { useState } from "react";
|
|
6
|
-
import { View, StyleSheet, Alert } from "react-native";
|
|
7
|
-
import {
|
|
8
|
-
ScreenLayout,
|
|
9
|
-
ScreenHeader,
|
|
10
|
-
AtomicInput,
|
|
11
|
-
AtomicButton,
|
|
12
|
-
AtomicText,
|
|
13
|
-
useAppDesignTokens,
|
|
14
|
-
} from "@umituz/react-native-design-system";
|
|
15
|
-
import {
|
|
16
|
-
updateUserPassword,
|
|
17
|
-
reauthenticateWithPassword,
|
|
18
|
-
getCurrentUserFromGlobal,
|
|
19
|
-
} from "@umituz/react-native-firebase";
|
|
20
|
-
import { RequirementItem } from "./RequirementItem";
|
|
21
|
-
import { validatePassword } from "./ChangePasswordScreen.types";
|
|
22
|
-
import type { ChangePasswordScreenProps } from "./ChangePasswordScreen.types";
|
|
23
|
-
|
|
24
|
-
export type { ChangePasswordTranslations, ChangePasswordScreenProps } from "./ChangePasswordScreen.types";
|
|
25
|
-
|
|
26
|
-
export const ChangePasswordScreen: React.FC<ChangePasswordScreenProps> = ({
|
|
27
|
-
translations,
|
|
28
|
-
onSuccess,
|
|
29
|
-
onCancel,
|
|
30
|
-
}) => {
|
|
31
|
-
const tokens = useAppDesignTokens();
|
|
32
|
-
|
|
33
|
-
const [currentPassword, setCurrentPassword] = useState("");
|
|
34
|
-
const [newPassword, setNewPassword] = useState("");
|
|
35
|
-
const [confirmPassword, setConfirmPassword] = useState("");
|
|
36
|
-
const [loading, setLoading] = useState(false);
|
|
37
|
-
const [error, setError] = useState<string | null>(null);
|
|
38
|
-
|
|
39
|
-
const validation = validatePassword(newPassword, confirmPassword, currentPassword);
|
|
40
|
-
|
|
41
|
-
const handleChangePassword = async () => {
|
|
42
|
-
if (!validation.isValid) {
|
|
43
|
-
Alert.alert("Error", translations.fillAllFields);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const user = getCurrentUserFromGlobal();
|
|
48
|
-
if (!user) {
|
|
49
|
-
setError(translations.unauthorized);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
setLoading(true);
|
|
54
|
-
setError(null);
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
const reauthResult = await reauthenticateWithPassword(user, currentPassword);
|
|
58
|
-
if (!reauthResult.success) {
|
|
59
|
-
setError(reauthResult.error?.message || translations.signInFailed);
|
|
60
|
-
setLoading(false);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const updateResult = await updateUserPassword(user, newPassword);
|
|
65
|
-
if (updateResult.success) {
|
|
66
|
-
Alert.alert("Success", translations.success, [
|
|
67
|
-
{ text: "OK", onPress: onSuccess }
|
|
68
|
-
]);
|
|
69
|
-
} else {
|
|
70
|
-
setError(updateResult.error?.message || translations.error);
|
|
71
|
-
}
|
|
72
|
-
} catch (e: unknown) {
|
|
73
|
-
const errorMessage = e instanceof Error ? e.message : translations.error;
|
|
74
|
-
setError(errorMessage);
|
|
75
|
-
} finally {
|
|
76
|
-
setLoading(false);
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<ScreenLayout
|
|
82
|
-
scrollable
|
|
83
|
-
header={<ScreenHeader title={translations.title} />}
|
|
84
|
-
backgroundColor={tokens.colors.backgroundPrimary}
|
|
85
|
-
edges={["bottom"]}
|
|
86
|
-
>
|
|
87
|
-
<View style={styles.content}>
|
|
88
|
-
<AtomicText type="bodyMedium" style={{ color: tokens.colors.textSecondary, marginBottom: 24 }}>
|
|
89
|
-
{translations.description}
|
|
90
|
-
</AtomicText>
|
|
91
|
-
|
|
92
|
-
<View style={styles.form}>
|
|
93
|
-
<AtomicInput
|
|
94
|
-
label={translations.currentPassword}
|
|
95
|
-
placeholder={translations.enterCurrentPassword}
|
|
96
|
-
value={currentPassword}
|
|
97
|
-
onChangeText={setCurrentPassword}
|
|
98
|
-
secureTextEntry
|
|
99
|
-
showPasswordToggle
|
|
100
|
-
variant="filled"
|
|
101
|
-
/>
|
|
102
|
-
|
|
103
|
-
<AtomicInput
|
|
104
|
-
label={translations.newPassword}
|
|
105
|
-
placeholder={translations.enterNewPassword}
|
|
106
|
-
value={newPassword}
|
|
107
|
-
onChangeText={setNewPassword}
|
|
108
|
-
secureTextEntry
|
|
109
|
-
showPasswordToggle
|
|
110
|
-
variant="filled"
|
|
111
|
-
/>
|
|
112
|
-
|
|
113
|
-
<AtomicInput
|
|
114
|
-
label={translations.confirmPassword}
|
|
115
|
-
placeholder={translations.enterConfirmPassword}
|
|
116
|
-
value={confirmPassword}
|
|
117
|
-
onChangeText={setConfirmPassword}
|
|
118
|
-
secureTextEntry
|
|
119
|
-
showPasswordToggle
|
|
120
|
-
variant="filled"
|
|
121
|
-
/>
|
|
122
|
-
|
|
123
|
-
<View style={[styles.requirementsContainer, { backgroundColor: tokens.colors.surfaceSecondary }]}>
|
|
124
|
-
<AtomicText type="labelMedium" style={{ color: tokens.colors.textSecondary, marginBottom: 8 }}>
|
|
125
|
-
{translations.requirements}
|
|
126
|
-
</AtomicText>
|
|
127
|
-
<RequirementItem label={translations.minLength} met={validation.isLengthValid} />
|
|
128
|
-
<RequirementItem label={translations.uppercase} met={validation.hasUppercase} />
|
|
129
|
-
<RequirementItem label={translations.lowercase} met={validation.hasLowercase} />
|
|
130
|
-
<RequirementItem label={translations.number} met={validation.hasNumber} />
|
|
131
|
-
<RequirementItem label={translations.specialChar} met={validation.hasSpecialChar} />
|
|
132
|
-
<RequirementItem label={translations.passwordsMatch} met={validation.passwordsMatch} />
|
|
133
|
-
</View>
|
|
134
|
-
|
|
135
|
-
{error && (
|
|
136
|
-
<AtomicText style={{ color: tokens.colors.error, marginTop: 16 }}>
|
|
137
|
-
{error}
|
|
138
|
-
</AtomicText>
|
|
139
|
-
)}
|
|
140
|
-
|
|
141
|
-
<View style={styles.actions}>
|
|
142
|
-
<AtomicButton
|
|
143
|
-
title={translations.cancel}
|
|
144
|
-
onPress={onCancel || (() => {})}
|
|
145
|
-
variant="outline"
|
|
146
|
-
style={{ flex: 1 }}
|
|
147
|
-
/>
|
|
148
|
-
<AtomicButton
|
|
149
|
-
title={loading ? translations.changing : translations.changePassword}
|
|
150
|
-
onPress={() => { void handleChangePassword(); }}
|
|
151
|
-
loading={loading}
|
|
152
|
-
disabled={!validation.isValid || loading}
|
|
153
|
-
style={{ flex: 1 }}
|
|
154
|
-
/>
|
|
155
|
-
</View>
|
|
156
|
-
</View>
|
|
157
|
-
</View>
|
|
158
|
-
</ScreenLayout>
|
|
159
|
-
);
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const styles = StyleSheet.create({
|
|
163
|
-
content: {
|
|
164
|
-
padding: 16,
|
|
165
|
-
},
|
|
166
|
-
form: {
|
|
167
|
-
gap: 16,
|
|
168
|
-
},
|
|
169
|
-
requirementsContainer: {
|
|
170
|
-
marginTop: 8,
|
|
171
|
-
padding: 12,
|
|
172
|
-
borderRadius: 8,
|
|
173
|
-
},
|
|
174
|
-
actions: {
|
|
175
|
-
flexDirection: "row",
|
|
176
|
-
gap: 12,
|
|
177
|
-
marginTop: 24,
|
|
178
|
-
},
|
|
179
|
-
});
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Change Password Screen Types
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface ChangePasswordTranslations {
|
|
6
|
-
title: string;
|
|
7
|
-
description: string;
|
|
8
|
-
currentPassword: string;
|
|
9
|
-
enterCurrentPassword: string;
|
|
10
|
-
newPassword: string;
|
|
11
|
-
enterNewPassword: string;
|
|
12
|
-
confirmPassword: string;
|
|
13
|
-
enterConfirmPassword: string;
|
|
14
|
-
requirements: string;
|
|
15
|
-
minLength: string;
|
|
16
|
-
uppercase: string;
|
|
17
|
-
lowercase: string;
|
|
18
|
-
number: string;
|
|
19
|
-
specialChar: string;
|
|
20
|
-
passwordsMatch: string;
|
|
21
|
-
changePassword: string;
|
|
22
|
-
changing: string;
|
|
23
|
-
cancel: string;
|
|
24
|
-
success: string;
|
|
25
|
-
error: string;
|
|
26
|
-
fillAllFields: string;
|
|
27
|
-
unauthorized: string;
|
|
28
|
-
signInFailed: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface ChangePasswordScreenProps {
|
|
32
|
-
translations: ChangePasswordTranslations;
|
|
33
|
-
onSuccess?: () => void;
|
|
34
|
-
onCancel?: () => void;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface PasswordValidation {
|
|
38
|
-
isLengthValid: boolean;
|
|
39
|
-
hasUppercase: boolean;
|
|
40
|
-
hasLowercase: boolean;
|
|
41
|
-
hasNumber: boolean;
|
|
42
|
-
hasSpecialChar: boolean;
|
|
43
|
-
passwordsMatch: boolean;
|
|
44
|
-
isValid: boolean;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function validatePassword(
|
|
48
|
-
newPassword: string,
|
|
49
|
-
confirmPassword: string,
|
|
50
|
-
currentPassword: string
|
|
51
|
-
): PasswordValidation {
|
|
52
|
-
const isLengthValid = newPassword.length >= 8;
|
|
53
|
-
const hasUppercase = /[A-Z]/.test(newPassword);
|
|
54
|
-
const hasLowercase = /[a-z]/.test(newPassword);
|
|
55
|
-
const hasNumber = /[0-9]/.test(newPassword);
|
|
56
|
-
const hasSpecialChar = /[!@#$%^&*]/.test(newPassword);
|
|
57
|
-
const passwordsMatch = newPassword === confirmPassword && newPassword !== "";
|
|
58
|
-
|
|
59
|
-
const isValid =
|
|
60
|
-
isLengthValid &&
|
|
61
|
-
hasUppercase &&
|
|
62
|
-
hasLowercase &&
|
|
63
|
-
hasNumber &&
|
|
64
|
-
hasSpecialChar &&
|
|
65
|
-
passwordsMatch &&
|
|
66
|
-
currentPassword.length > 0;
|
|
67
|
-
|
|
68
|
-
return {
|
|
69
|
-
isLengthValid,
|
|
70
|
-
hasUppercase,
|
|
71
|
-
hasLowercase,
|
|
72
|
-
hasNumber,
|
|
73
|
-
hasSpecialChar,
|
|
74
|
-
passwordsMatch,
|
|
75
|
-
isValid,
|
|
76
|
-
};
|
|
77
|
-
}
|