@umituz/react-native-auth 1.11.0 → 2.0.1

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.
Files changed (136) hide show
  1. package/README.md +0 -0
  2. package/lib/__tests__/services/AuthCoreService.test.d.ts +4 -0
  3. package/lib/__tests__/services/AuthCoreService.test.js +198 -0
  4. package/lib/__tests__/services/AuthPackage.test.d.ts +4 -0
  5. package/lib/__tests__/services/AuthPackage.test.js +177 -0
  6. package/lib/__tests__/services/GuestModeService.test.d.ts +4 -0
  7. package/lib/__tests__/services/GuestModeService.test.js +141 -0
  8. package/lib/__tests__/utils/AuthValidation.test.d.ts +4 -0
  9. package/lib/__tests__/utils/AuthValidation.test.js +222 -0
  10. package/lib/application/ports/IAuthProvider.d.ts +42 -0
  11. package/lib/application/ports/IAuthProvider.js +5 -0
  12. package/lib/application/ports/IAuthService.d.ts +48 -0
  13. package/lib/application/ports/IAuthService.js +5 -0
  14. package/lib/domain/entities/AuthUser.d.ts +12 -0
  15. package/lib/domain/entities/AuthUser.js +5 -0
  16. package/lib/domain/errors/AuthError.d.ts +36 -0
  17. package/lib/domain/errors/AuthError.js +76 -0
  18. package/lib/domain/value-objects/AuthConfig.d.ts +16 -0
  19. package/lib/domain/value-objects/AuthConfig.js +14 -0
  20. package/lib/index.d.ts +45 -0
  21. package/lib/index.js +59 -0
  22. package/lib/infrastructure/adapters/StorageProviderAdapter.d.ts +16 -0
  23. package/lib/infrastructure/adapters/StorageProviderAdapter.js +72 -0
  24. package/lib/infrastructure/adapters/UIProviderAdapter.d.ts +18 -0
  25. package/lib/infrastructure/adapters/UIProviderAdapter.js +28 -0
  26. package/lib/infrastructure/providers/FirebaseAuthProvider.d.ts +19 -0
  27. package/lib/infrastructure/providers/FirebaseAuthProvider.js +94 -0
  28. package/lib/infrastructure/services/AuthCoreService.d.ts +22 -0
  29. package/lib/infrastructure/services/AuthCoreService.js +102 -0
  30. package/lib/infrastructure/services/AuthEventService.d.ts +28 -0
  31. package/lib/infrastructure/services/AuthEventService.js +88 -0
  32. package/lib/infrastructure/services/AuthPackage.d.ts +62 -0
  33. package/lib/infrastructure/services/AuthPackage.js +91 -0
  34. package/lib/infrastructure/services/AuthService.d.ts +42 -0
  35. package/lib/infrastructure/services/AuthService.js +123 -0
  36. package/lib/infrastructure/services/GuestModeService.d.ts +23 -0
  37. package/lib/infrastructure/services/GuestModeService.js +69 -0
  38. package/lib/infrastructure/storage/GuestModeStorage.d.ts +16 -0
  39. package/lib/infrastructure/storage/GuestModeStorage.js +73 -0
  40. package/lib/infrastructure/utils/AuthErrorMapper.d.ts +8 -0
  41. package/lib/infrastructure/utils/AuthErrorMapper.js +51 -0
  42. package/lib/infrastructure/utils/AuthEventEmitter.d.ts +12 -0
  43. package/lib/infrastructure/utils/AuthEventEmitter.js +25 -0
  44. package/lib/infrastructure/utils/AuthValidation.d.ts +49 -0
  45. package/lib/infrastructure/utils/AuthValidation.js +133 -0
  46. package/lib/infrastructure/utils/UserMapper.d.ts +15 -0
  47. package/lib/infrastructure/utils/UserMapper.js +16 -0
  48. package/lib/presentation/components/AuthContainer.d.ts +10 -0
  49. package/lib/presentation/components/AuthContainer.js +27 -0
  50. package/lib/presentation/components/AuthDivider.d.ts +6 -0
  51. package/lib/presentation/components/AuthDivider.js +36 -0
  52. package/lib/presentation/components/AuthErrorDisplay.d.ts +10 -0
  53. package/lib/presentation/components/AuthErrorDisplay.js +24 -0
  54. package/lib/presentation/components/AuthFormCard.d.ts +10 -0
  55. package/lib/presentation/components/AuthFormCard.js +19 -0
  56. package/lib/presentation/components/AuthGradientBackground.d.ts +6 -0
  57. package/lib/presentation/components/AuthGradientBackground.js +8 -0
  58. package/lib/presentation/components/AuthHeader.d.ts +11 -0
  59. package/lib/presentation/components/AuthHeader.js +38 -0
  60. package/lib/presentation/components/AuthLegalLinks.d.ts +28 -0
  61. package/lib/presentation/components/AuthLegalLinks.js +54 -0
  62. package/lib/presentation/components/AuthLink.d.ts +13 -0
  63. package/lib/presentation/components/AuthLink.js +27 -0
  64. package/lib/presentation/components/LoginForm.d.ts +10 -0
  65. package/lib/presentation/components/LoginForm.js +27 -0
  66. package/lib/presentation/components/PasswordMatchIndicator.d.ts +9 -0
  67. package/lib/presentation/components/PasswordMatchIndicator.js +30 -0
  68. package/lib/presentation/components/PasswordStrengthIndicator.d.ts +11 -0
  69. package/lib/presentation/components/PasswordStrengthIndicator.js +60 -0
  70. package/lib/presentation/components/RegisterForm.d.ts +14 -0
  71. package/lib/presentation/components/RegisterForm.js +30 -0
  72. package/lib/presentation/hooks/useAuth.d.ts +44 -0
  73. package/lib/presentation/hooks/useAuth.js +38 -0
  74. package/lib/presentation/hooks/useAuthActions.d.ts +15 -0
  75. package/lib/presentation/hooks/useAuthActions.js +162 -0
  76. package/lib/presentation/hooks/useAuthState.d.ts +19 -0
  77. package/lib/presentation/hooks/useAuthState.js +79 -0
  78. package/lib/presentation/hooks/useLoginForm.d.ts +21 -0
  79. package/lib/presentation/hooks/useLoginForm.js +131 -0
  80. package/lib/presentation/hooks/useRegisterForm.d.ts +31 -0
  81. package/lib/presentation/hooks/useRegisterForm.js +136 -0
  82. package/lib/presentation/navigation/AuthNavigator.d.ts +28 -0
  83. package/lib/presentation/navigation/AuthNavigator.js +37 -0
  84. package/lib/presentation/screens/LoginScreen.d.ts +6 -0
  85. package/lib/presentation/screens/LoginScreen.js +15 -0
  86. package/lib/presentation/screens/RegisterScreen.d.ts +12 -0
  87. package/lib/presentation/screens/RegisterScreen.js +15 -0
  88. package/lib/presentation/utils/getAuthErrorMessage.d.ts +8 -0
  89. package/lib/presentation/utils/getAuthErrorMessage.js +69 -0
  90. package/package.json +12 -4
  91. package/src/__tests__/services/AuthCoreService.test.ts +247 -0
  92. package/src/__tests__/services/AuthPackage.test.ts +226 -0
  93. package/src/__tests__/services/GuestModeService.test.ts +194 -0
  94. package/src/__tests__/utils/AuthValidation.test.ts +270 -0
  95. package/src/application/ports/IAuthProvider.ts +0 -0
  96. package/src/application/ports/IAuthService.ts +0 -0
  97. package/src/domain/entities/AuthUser.ts +0 -0
  98. package/src/domain/errors/AuthError.ts +0 -0
  99. package/src/domain/value-objects/AuthConfig.ts +0 -0
  100. package/src/index.ts +4 -0
  101. package/src/infrastructure/adapters/StorageProviderAdapter.ts +73 -0
  102. package/src/infrastructure/adapters/UIProviderAdapter.ts +39 -0
  103. package/src/infrastructure/providers/FirebaseAuthProvider.ts +10 -2
  104. package/src/infrastructure/services/AuthCoreService.ts +138 -0
  105. package/src/infrastructure/services/AuthEventService.ts +115 -0
  106. package/src/infrastructure/services/AuthPackage.ts +148 -0
  107. package/src/infrastructure/services/AuthService.ts +62 -128
  108. package/src/infrastructure/services/GuestModeService.ts +86 -0
  109. package/src/infrastructure/storage/GuestModeStorage.ts +40 -14
  110. package/src/infrastructure/utils/AuthErrorMapper.ts +7 -3
  111. package/src/infrastructure/utils/AuthEventEmitter.ts +0 -0
  112. package/src/infrastructure/utils/AuthValidation.ts +47 -17
  113. package/src/infrastructure/utils/UserMapper.ts +0 -0
  114. package/src/presentation/components/AuthContainer.tsx +0 -0
  115. package/src/presentation/components/AuthDivider.tsx +0 -0
  116. package/src/presentation/components/AuthErrorDisplay.tsx +0 -0
  117. package/src/presentation/components/AuthFormCard.tsx +0 -0
  118. package/src/presentation/components/AuthGradientBackground.tsx +0 -0
  119. package/src/presentation/components/AuthHeader.tsx +0 -0
  120. package/src/presentation/components/AuthLegalLinks.tsx +0 -0
  121. package/src/presentation/components/AuthLink.tsx +0 -0
  122. package/src/presentation/components/LoginForm.tsx +0 -0
  123. package/src/presentation/components/PasswordMatchIndicator.tsx +50 -0
  124. package/src/presentation/components/PasswordStrengthIndicator.tsx +118 -0
  125. package/src/presentation/components/RegisterForm.tsx +10 -0
  126. package/src/presentation/hooks/useAuth.ts +0 -0
  127. package/src/presentation/hooks/useAuthActions.ts +8 -11
  128. package/src/presentation/hooks/useAuthState.ts +10 -0
  129. package/src/presentation/hooks/useLoginForm.ts +0 -0
  130. package/src/presentation/hooks/useRegisterForm.ts +40 -18
  131. package/src/presentation/navigation/AuthNavigator.tsx +2 -2
  132. package/src/presentation/screens/LoginScreen.tsx +3 -6
  133. package/src/presentation/screens/RegisterScreen.tsx +3 -6
  134. package/src/presentation/utils/getAuthErrorMessage.ts +0 -0
  135. package/src/types/external.d.ts +68 -0
  136. package/LICENSE +0 -22
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Guest Mode Service
3
+ * Handles guest mode functionality
4
+ */
5
+
6
+ import type { IAuthProvider } from "../../application/ports/IAuthProvider";
7
+ import type { AuthUser } from "../../domain/entities/AuthUser";
8
+ import { emitGuestModeEnabled } from "../utils/AuthEventEmitter";
9
+
10
+ export interface IStorageProvider {
11
+ get(key: string): Promise<string | null>;
12
+ set(key: string, value: string): Promise<void>;
13
+ remove(key: string): Promise<void>;
14
+ }
15
+
16
+ export class GuestModeService {
17
+ private isGuestMode: boolean = false;
18
+ private storageKey: string;
19
+
20
+ constructor(storageKey: string = "@auth_guest_mode") {
21
+ this.storageKey = storageKey;
22
+ }
23
+
24
+ async load(storageProvider: IStorageProvider): Promise<boolean> {
25
+ try {
26
+ const value = await storageProvider.get(this.storageKey);
27
+ this.isGuestMode = value === "true";
28
+ return this.isGuestMode;
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ async save(storageProvider: IStorageProvider): Promise<void> {
35
+ try {
36
+ await storageProvider.set(this.storageKey, this.isGuestMode.toString());
37
+ } catch {
38
+ // Silently fail storage operations
39
+ }
40
+ }
41
+
42
+ async clear(storageProvider: IStorageProvider): Promise<void> {
43
+ try {
44
+ await storageProvider.remove(this.storageKey);
45
+ } catch {
46
+ // Silently fail storage operations
47
+ }
48
+ this.isGuestMode = false;
49
+ }
50
+
51
+ async enable(storageProvider: IStorageProvider, provider?: IAuthProvider): Promise<void> {
52
+ // Sign out from provider if logged in
53
+ if (provider?.getCurrentUser()) {
54
+ try {
55
+ await provider.signOut();
56
+ } catch {
57
+ // Ignore sign out errors when switching to guest mode
58
+ }
59
+ }
60
+
61
+ this.isGuestMode = true;
62
+ await this.save(storageProvider);
63
+ emitGuestModeEnabled();
64
+ }
65
+
66
+ getIsGuestMode(): boolean {
67
+ return this.isGuestMode;
68
+ }
69
+
70
+ setGuestMode(enabled: boolean): void {
71
+ this.isGuestMode = enabled;
72
+ }
73
+
74
+ wrapAuthStateCallback(
75
+ callback: (user: AuthUser | null) => void
76
+ ): (user: AuthUser | null) => void {
77
+ return (user: AuthUser | null) => {
78
+ // Don't update if in guest mode
79
+ if (!this.isGuestMode) {
80
+ callback(user);
81
+ } else {
82
+ callback(null);
83
+ }
84
+ };
85
+ }
86
+ }
@@ -3,18 +3,27 @@
3
3
  * Single Responsibility: Manage guest mode persistence
4
4
  */
5
5
 
6
- import { storageRepository, unwrap } from "@umituz/react-native-storage";
7
-
8
- const GUEST_MODE_KEY = "@auth_guest_mode";
6
+ import type { IStorageProvider } from "../services/GuestModeService";
7
+ import { getAuthPackage } from "../services/AuthPackage";
9
8
 
10
9
  /**
11
10
  * Load guest mode from storage
12
11
  */
13
- export async function loadGuestMode(): Promise<boolean> {
12
+ export async function loadGuestMode(storageKey?: string): Promise<boolean> {
14
13
  try {
15
- const result = await storageRepository.getString(GUEST_MODE_KEY, "false");
16
- const guestModeValue = unwrap(result, "false");
17
- return guestModeValue === "true";
14
+ const packageConfig = getAuthPackage()?.getConfig();
15
+ const key = storageKey ?? packageConfig?.storageKeys.guestMode ?? "@auth_guest_mode";
16
+
17
+ const storageProvider = getAuthPackage()?.getStorageProvider();
18
+ if (!storageProvider) {
19
+ if (__DEV__) {
20
+ console.warn("[GuestModeStorage] No storage provider available");
21
+ }
22
+ return false;
23
+ }
24
+
25
+ const value = await storageProvider.get(key);
26
+ return value === "true";
18
27
  } catch {
19
28
  return false;
20
29
  }
@@ -23,19 +32,28 @@ export async function loadGuestMode(): Promise<boolean> {
23
32
  /**
24
33
  * Save guest mode to storage
25
34
  */
26
- export async function saveGuestMode(isGuest: boolean): Promise<void> {
35
+ export async function saveGuestMode(isGuest: boolean, storageKey?: string): Promise<void> {
27
36
  try {
37
+ const packageConfig = getAuthPackage()?.getConfig();
38
+ const key = storageKey ?? packageConfig?.storageKeys.guestMode ?? "@auth_guest_mode";
39
+
40
+ const storageProvider = getAuthPackage()?.getStorageProvider();
41
+ if (!storageProvider) {
42
+ if (__DEV__) {
43
+ console.warn("[GuestModeStorage] No storage provider available");
44
+ }
45
+ return;
46
+ }
47
+
28
48
  if (isGuest) {
29
- await storageRepository.setString(GUEST_MODE_KEY, "true");
30
- /* eslint-disable-next-line no-console */
49
+ await storageProvider.set(key, "true");
31
50
  if (__DEV__) {
32
51
  console.log("[GuestModeStorage] Guest mode persisted to storage");
33
52
  }
34
53
  } else {
35
- await storageRepository.removeItem(GUEST_MODE_KEY);
54
+ await storageProvider.remove(key);
36
55
  }
37
56
  } catch (error) {
38
- /* eslint-disable-next-line no-console */
39
57
  if (__DEV__) {
40
58
  console.warn("[GuestModeStorage] Failed to persist guest mode:", error);
41
59
  }
@@ -45,9 +63,17 @@ export async function saveGuestMode(isGuest: boolean): Promise<void> {
45
63
  /**
46
64
  * Clear guest mode from storage
47
65
  */
48
- export async function clearGuestMode(): Promise<void> {
66
+ export async function clearGuestMode(storageKey?: string): Promise<void> {
49
67
  try {
50
- await storageRepository.removeItem(GUEST_MODE_KEY);
68
+ const packageConfig = getAuthPackage()?.getConfig();
69
+ const key = storageKey ?? packageConfig?.storageKeys.guestMode ?? "@auth_guest_mode";
70
+
71
+ const storageProvider = getAuthPackage()?.getStorageProvider();
72
+ if (!storageProvider) {
73
+ return;
74
+ }
75
+
76
+ await storageProvider.remove(key);
51
77
  } catch {
52
78
  // Ignore storage errors
53
79
  }
@@ -18,9 +18,13 @@ import {
18
18
  * Map Firebase Auth errors to domain errors
19
19
  */
20
20
  export function mapFirebaseAuthError(error: unknown): Error {
21
- const errorObj = error as { code?: string; message?: string } | undefined;
22
- const code = errorObj?.code || "";
23
- const message = errorObj?.message || "Authentication failed";
21
+ if (!error || typeof error !== 'object') {
22
+ return new AuthError("Authentication failed", "AUTH_UNKNOWN_ERROR");
23
+ }
24
+
25
+ const errorObj = error as { code?: string; message?: string };
26
+ const code = errorObj.code || "";
27
+ const message = errorObj.message || "Authentication failed";
24
28
 
25
29
  switch (code) {
26
30
  case "auth/email-already-in-use":
File without changes
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import type { PasswordConfig } from "../../domain/value-objects/AuthConfig";
7
+ import { getAuthPackage } from "../services/AuthPackage";
7
8
 
8
9
  export interface ValidationResult {
9
10
  isValid: boolean;
@@ -22,11 +23,35 @@ export interface PasswordRequirements {
22
23
  hasSpecialChar: boolean;
23
24
  }
24
25
 
25
- const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
26
- const UPPERCASE_REGEX = /[A-Z]/;
27
- const LOWERCASE_REGEX = /[a-z]/;
28
- const NUMBER_REGEX = /[0-9]/;
29
- const SPECIAL_CHAR_REGEX = /[!@#$%^&*(),.?":{}|<>]/;
26
+ export interface ValidationConfig {
27
+ emailRegex: RegExp;
28
+ uppercaseRegex: RegExp;
29
+ lowercaseRegex: RegExp;
30
+ numberRegex: RegExp;
31
+ specialCharRegex: RegExp;
32
+ displayNameMinLength: number;
33
+ }
34
+
35
+ const DEFAULT_VALIDATION_CONFIG: ValidationConfig = {
36
+ emailRegex: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
37
+ uppercaseRegex: /[A-Z]/,
38
+ lowercaseRegex: /[a-z]/,
39
+ numberRegex: /[0-9]/,
40
+ specialCharRegex: /[!@#$%^&*(),.?":{}|<>]/,
41
+ displayNameMinLength: 2,
42
+ };
43
+
44
+ function getValidationConfig(): ValidationConfig {
45
+ const packageConfig = getAuthPackage()?.getConfig();
46
+ return {
47
+ emailRegex: packageConfig?.validation.emailRegex || DEFAULT_VALIDATION_CONFIG.emailRegex,
48
+ uppercaseRegex: DEFAULT_VALIDATION_CONFIG.uppercaseRegex,
49
+ lowercaseRegex: DEFAULT_VALIDATION_CONFIG.lowercaseRegex,
50
+ numberRegex: DEFAULT_VALIDATION_CONFIG.numberRegex,
51
+ specialCharRegex: DEFAULT_VALIDATION_CONFIG.specialCharRegex,
52
+ displayNameMinLength: DEFAULT_VALIDATION_CONFIG.displayNameMinLength,
53
+ };
54
+ }
30
55
 
31
56
  /**
32
57
  * Validate email format
@@ -36,7 +61,8 @@ export function validateEmail(email: string): ValidationResult {
36
61
  return { isValid: false, error: "Email is required" };
37
62
  }
38
63
 
39
- if (!EMAIL_REGEX.test(email.trim())) {
64
+ const config = getValidationConfig();
65
+ if (!config.emailRegex.test(email.trim())) {
40
66
  return { isValid: false, error: "Please enter a valid email address" };
41
67
  }
42
68
 
@@ -63,13 +89,14 @@ export function validatePasswordForRegister(
63
89
  password: string,
64
90
  config: PasswordConfig
65
91
  ): PasswordStrengthResult {
92
+ const validationConfig = getValidationConfig();
66
93
  const requirements: PasswordRequirements = {
67
94
  hasMinLength: password.length >= config.minLength,
68
- hasUppercase: !config.requireUppercase || UPPERCASE_REGEX.test(password),
69
- hasLowercase: !config.requireLowercase || LOWERCASE_REGEX.test(password),
70
- hasNumber: !config.requireNumber || NUMBER_REGEX.test(password),
95
+ hasUppercase: !config.requireUppercase || validationConfig.uppercaseRegex.test(password),
96
+ hasLowercase: !config.requireLowercase || validationConfig.lowercaseRegex.test(password),
97
+ hasNumber: !config.requireNumber || validationConfig.numberRegex.test(password),
71
98
  hasSpecialChar:
72
- !config.requireSpecialChar || SPECIAL_CHAR_REGEX.test(password),
99
+ !config.requireSpecialChar || validationConfig.specialCharRegex.test(password),
73
100
  };
74
101
 
75
102
  if (!password || password.length === 0) {
@@ -88,7 +115,7 @@ export function validatePasswordForRegister(
88
115
  };
89
116
  }
90
117
 
91
- if (config.requireUppercase && !UPPERCASE_REGEX.test(password)) {
118
+ if (config.requireUppercase && !validationConfig.uppercaseRegex.test(password)) {
92
119
  return {
93
120
  isValid: false,
94
121
  error: "Password must contain at least one uppercase letter",
@@ -96,7 +123,7 @@ export function validatePasswordForRegister(
96
123
  };
97
124
  }
98
125
 
99
- if (config.requireLowercase && !LOWERCASE_REGEX.test(password)) {
126
+ if (config.requireLowercase && !validationConfig.lowercaseRegex.test(password)) {
100
127
  return {
101
128
  isValid: false,
102
129
  error: "Password must contain at least one lowercase letter",
@@ -104,7 +131,7 @@ export function validatePasswordForRegister(
104
131
  };
105
132
  }
106
133
 
107
- if (config.requireNumber && !NUMBER_REGEX.test(password)) {
134
+ if (config.requireNumber && !validationConfig.numberRegex.test(password)) {
108
135
  return {
109
136
  isValid: false,
110
137
  error: "Password must contain at least one number",
@@ -112,7 +139,7 @@ export function validatePasswordForRegister(
112
139
  };
113
140
  }
114
141
 
115
- if (config.requireSpecialChar && !SPECIAL_CHAR_REGEX.test(password)) {
142
+ if (config.requireSpecialChar && !validationConfig.specialCharRegex.test(password)) {
116
143
  return {
117
144
  isValid: false,
118
145
  error: "Password must contain at least one special character",
@@ -146,16 +173,19 @@ export function validatePasswordConfirmation(
146
173
  */
147
174
  export function validateDisplayName(
148
175
  displayName: string,
149
- minLength: number = 2
176
+ minLength?: number
150
177
  ): ValidationResult {
151
178
  if (!displayName || displayName.trim() === "") {
152
179
  return { isValid: false, error: "Name is required" };
153
180
  }
154
181
 
155
- if (displayName.trim().length < minLength) {
182
+ const config = getValidationConfig();
183
+ const actualMinLength = minLength ?? config.displayNameMinLength;
184
+
185
+ if (displayName.trim().length < actualMinLength) {
156
186
  return {
157
187
  isValid: false,
158
- error: `Name must be at least ${minLength} characters`,
188
+ error: `Name must be at least ${actualMinLength} characters`,
159
189
  };
160
190
  }
161
191
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Password Match Indicator Component
3
+ * Shows whether passwords match
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, Text, StyleSheet } from "react-native";
8
+ import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
9
+ import { useLocalization } from "@umituz/react-native-localization";
10
+
11
+ export interface PasswordMatchIndicatorProps {
12
+ isMatch: boolean;
13
+ }
14
+
15
+ export const PasswordMatchIndicator: React.FC<PasswordMatchIndicatorProps> = ({
16
+ isMatch,
17
+ }) => {
18
+ const tokens = useAppDesignTokens();
19
+ const { t } = useLocalization();
20
+
21
+ const color = isMatch ? tokens.colors.success : tokens.colors.error;
22
+ const text = isMatch
23
+ ? t("auth.passwordsMatch", { defaultValue: "Passwords match" })
24
+ : t("auth.passwordsDontMatch", { defaultValue: "Passwords don't match" });
25
+
26
+ return (
27
+ <View style={styles.container}>
28
+ <View style={[styles.dot, { backgroundColor: color }]} />
29
+ <Text style={[styles.text, { color }]}>{text}</Text>
30
+ </View>
31
+ );
32
+ };
33
+
34
+ const styles = StyleSheet.create({
35
+ container: {
36
+ flexDirection: "row",
37
+ alignItems: "center",
38
+ gap: 6,
39
+ marginTop: 8,
40
+ },
41
+ dot: {
42
+ width: 6,
43
+ height: 6,
44
+ borderRadius: 3,
45
+ },
46
+ text: {
47
+ fontSize: 12,
48
+ fontWeight: "500",
49
+ },
50
+ });
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Password Strength Indicator Component
3
+ * Shows password requirements with visual feedback
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, Text, StyleSheet } from "react-native";
8
+ import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
9
+ import type { PasswordRequirements } from "../../infrastructure/utils/AuthValidation";
10
+
11
+ export interface PasswordStrengthIndicatorProps {
12
+ requirements: PasswordRequirements;
13
+ showLabels?: boolean;
14
+ }
15
+
16
+ interface RequirementDotProps {
17
+ label: string;
18
+ isValid: boolean;
19
+ successColor: string;
20
+ pendingColor: string;
21
+ }
22
+
23
+ const RequirementDot: React.FC<RequirementDotProps> = ({
24
+ label,
25
+ isValid,
26
+ successColor,
27
+ pendingColor,
28
+ }) => {
29
+ const color = isValid ? successColor : pendingColor;
30
+
31
+ return (
32
+ <View style={styles.requirement}>
33
+ <View style={[styles.dot, { backgroundColor: color }]} />
34
+ <Text style={[styles.label, { color }]}>{label}</Text>
35
+ </View>
36
+ );
37
+ };
38
+
39
+ export const PasswordStrengthIndicator: React.FC<
40
+ PasswordStrengthIndicatorProps
41
+ > = ({ requirements, showLabels = true }) => {
42
+ const tokens = useAppDesignTokens();
43
+ const successColor = tokens.colors.success;
44
+ const pendingColor = tokens.colors.textTertiary;
45
+
46
+ const items = [
47
+ { key: "minLength", label: "8+", isValid: requirements.hasMinLength },
48
+ { key: "uppercase", label: "A-Z", isValid: requirements.hasUppercase },
49
+ { key: "lowercase", label: "a-z", isValid: requirements.hasLowercase },
50
+ { key: "number", label: "0-9", isValid: requirements.hasNumber },
51
+ { key: "special", label: "!@#", isValid: requirements.hasSpecialChar },
52
+ ];
53
+
54
+ if (!showLabels) {
55
+ return (
56
+ <View style={styles.dotsOnly}>
57
+ {items.map((item) => (
58
+ <View
59
+ key={item.key}
60
+ style={[
61
+ styles.dotOnly,
62
+ {
63
+ backgroundColor: item.isValid ? successColor : pendingColor,
64
+ },
65
+ ]}
66
+ />
67
+ ))}
68
+ </View>
69
+ );
70
+ }
71
+
72
+ return (
73
+ <View style={styles.container}>
74
+ {items.map((item) => (
75
+ <RequirementDot
76
+ key={item.key}
77
+ label={item.label}
78
+ isValid={item.isValid}
79
+ successColor={successColor}
80
+ pendingColor={pendingColor}
81
+ />
82
+ ))}
83
+ </View>
84
+ );
85
+ };
86
+
87
+ const styles = StyleSheet.create({
88
+ container: {
89
+ flexDirection: "row",
90
+ flexWrap: "wrap",
91
+ gap: 12,
92
+ marginTop: 8,
93
+ },
94
+ dotsOnly: {
95
+ flexDirection: "row",
96
+ gap: 6,
97
+ marginTop: 8,
98
+ },
99
+ requirement: {
100
+ flexDirection: "row",
101
+ alignItems: "center",
102
+ gap: 4,
103
+ },
104
+ dot: {
105
+ width: 6,
106
+ height: 6,
107
+ borderRadius: 3,
108
+ },
109
+ dotOnly: {
110
+ width: 8,
111
+ height: 8,
112
+ borderRadius: 4,
113
+ },
114
+ label: {
115
+ fontSize: 11,
116
+ fontWeight: "500",
117
+ },
118
+ });
@@ -11,6 +11,8 @@ import { useRegisterForm } from "../hooks/useRegisterForm";
11
11
  import { AuthErrorDisplay } from "./AuthErrorDisplay";
12
12
  import { AuthLink } from "./AuthLink";
13
13
  import { AuthLegalLinks } from "./AuthLegalLinks";
14
+ import { PasswordStrengthIndicator } from "./PasswordStrengthIndicator";
15
+ import { PasswordMatchIndicator } from "./PasswordMatchIndicator";
14
16
 
15
17
  interface RegisterFormProps {
16
18
  onNavigateToLogin: () => void;
@@ -35,6 +37,8 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
35
37
  confirmPassword,
36
38
  fieldErrors,
37
39
  loading,
40
+ passwordRequirements,
41
+ passwordsMatch,
38
42
  handleDisplayNameChange,
39
43
  handleEmailChange,
40
44
  handlePasswordChange,
@@ -86,6 +90,9 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
86
90
  state={fieldErrors.password ? "error" : "default"}
87
91
  helperText={fieldErrors.password || undefined}
88
92
  />
93
+ {password.length > 0 && (
94
+ <PasswordStrengthIndicator requirements={passwordRequirements} />
95
+ )}
89
96
  </View>
90
97
 
91
98
  <View style={styles.inputContainer}>
@@ -102,6 +109,9 @@ export const RegisterForm: React.FC<RegisterFormProps> = ({
102
109
  state={fieldErrors.confirmPassword ? "error" : "default"}
103
110
  helperText={fieldErrors.confirmPassword || undefined}
104
111
  />
112
+ {confirmPassword.length > 0 && (
113
+ <PasswordMatchIndicator isMatch={passwordsMatch} />
114
+ )}
105
115
  </View>
106
116
 
107
117
  <AuthErrorDisplay error={displayError} />
File without changes
@@ -40,8 +40,8 @@ export function useAuthActions(
40
40
  if (isGuest) {
41
41
  setIsGuest(false);
42
42
  }
43
- } catch (err: any) {
44
- const errorMessage = err.message || "Sign up failed";
43
+ } catch (err: unknown) {
44
+ const errorMessage = err instanceof Error ? err.message : "Sign up failed";
45
45
  setError(errorMessage);
46
46
  throw err;
47
47
  } finally {
@@ -79,12 +79,8 @@ export function useAuthActions(
79
79
  }
80
80
  setIsGuest(false);
81
81
  }
82
- } catch (err: any) {
83
- /* eslint-disable-next-line no-console */
84
- if (__DEV__) {
85
- console.error("[useAuthActions] Error in signIn:", err);
86
- }
87
- const errorMessage = err.message || "Failed to sign in";
82
+ } catch (err: unknown) {
83
+ const errorMessage = err instanceof Error ? err.message : "Sign in failed";
88
84
  setError(errorMessage);
89
85
  throw err;
90
86
  } finally {
@@ -152,12 +148,13 @@ export function useAuthActions(
152
148
  if (__DEV__) {
153
149
  console.log("[useAuthActions] ✅ isGuest set to true");
154
150
  }
155
- } catch (error) {
156
- /* eslint-disable-next-line no-console */
151
+ } catch (error: unknown) {
152
+ const errorMessage = error instanceof Error ? error.message : "Failed to continue as guest";
153
+ setError(errorMessage);
157
154
  if (__DEV__) {
158
155
  console.error("[useAuthActions] ❌ ERROR in continueAsGuest:", error);
159
156
  console.error("[useAuthActions] Error details:", {
160
- message: error instanceof Error ? error.message : String(error),
157
+ message: errorMessage,
161
158
  stack: error instanceof Error ? error.stack : undefined,
162
159
  });
163
160
  }
@@ -83,9 +83,19 @@ export function useAuthState(): UseAuthStateResult {
83
83
  }
84
84
  );
85
85
 
86
+ const errorSubscription = DeviceEventEmitter.addListener(
87
+ "auth-error",
88
+ (payload: any) => {
89
+ if (payload?.error) {
90
+ setError(payload.error);
91
+ }
92
+ }
93
+ );
94
+
86
95
  return () => {
87
96
  guestSubscription.remove();
88
97
  authSubscription.remove();
98
+ errorSubscription.remove();
89
99
  };
90
100
  }, []);
91
101
 
File without changes