@umituz/react-native-auth 1.10.0 → 1.12.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-auth",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design supports Firebase Auth and can be adapted for Supabase or other providers.",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
package/src/index.ts CHANGED
@@ -122,6 +122,10 @@ export { LoginForm } from './presentation/components/LoginForm';
122
122
  export { RegisterForm } from './presentation/components/RegisterForm';
123
123
  export { AuthLegalLinks } from './presentation/components/AuthLegalLinks';
124
124
  export type { AuthLegalLinksProps } from './presentation/components/AuthLegalLinks';
125
+ export { PasswordStrengthIndicator } from './presentation/components/PasswordStrengthIndicator';
126
+ export type { PasswordStrengthIndicatorProps } from './presentation/components/PasswordStrengthIndicator';
127
+ export { PasswordMatchIndicator } from './presentation/components/PasswordMatchIndicator';
128
+ export type { PasswordMatchIndicatorProps } from './presentation/components/PasswordMatchIndicator';
125
129
 
126
130
  // =============================================================================
127
131
  // PRESENTATION LAYER - Utilities
@@ -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", "Passwords match")
24
+ : t("auth.passwordsDontMatch", "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} />
@@ -3,7 +3,7 @@
3
3
  * Single Responsibility: Manage authentication state
4
4
  */
5
5
 
6
- import { useState, useEffect, useRef } from "react";
6
+ import { useState, useEffect, useRef, useMemo } from "react";
7
7
  import { DeviceEventEmitter } from "react-native";
8
8
  import { getAuthService } from "../../infrastructure/services/AuthService";
9
9
  import { useFirebaseAuth } from "@umituz/react-native-firebase-auth";
@@ -36,7 +36,12 @@ export function useAuthState(): UseAuthStateResult {
36
36
  // Ref to track latest isGuest value for event handlers
37
37
  const isGuestRef = useRef(isGuest);
38
38
 
39
- const user = isGuest ? null : mapToAuthUser(firebaseUser);
39
+ // Memoize user to prevent new object reference on every render
40
+ const user = useMemo(() => {
41
+ if (isGuest) return null;
42
+ return mapToAuthUser(firebaseUser);
43
+ }, [isGuest, firebaseUser?.uid]);
44
+
40
45
  const isAuthenticated = !!user && !isGuest;
41
46
 
42
47
  // Keep ref in sync with state
@@ -3,7 +3,7 @@
3
3
  * Single Responsibility: Handle register form logic
4
4
  */
5
5
 
6
- import { useState, useCallback } from "react";
6
+ import { useState, useCallback, useMemo } from "react";
7
7
  import { useLocalization } from "@umituz/react-native-localization";
8
8
  import { batchValidate } from "@umituz/react-native-validation";
9
9
  import {
@@ -14,6 +14,7 @@ import {
14
14
  import { DEFAULT_PASSWORD_CONFIG } from "../../domain/value-objects/AuthConfig";
15
15
  import { useAuth } from "./useAuth";
16
16
  import { getAuthErrorLocalizationKey } from "../utils/getAuthErrorMessage";
17
+ import type { PasswordRequirements } from "../../infrastructure/utils/AuthValidation";
17
18
 
18
19
  export interface UseRegisterFormResult {
19
20
  displayName: string;
@@ -28,6 +29,8 @@ export interface UseRegisterFormResult {
28
29
  };
29
30
  localError: string | null;
30
31
  loading: boolean;
32
+ passwordRequirements: PasswordRequirements;
33
+ passwordsMatch: boolean;
31
34
  handleDisplayNameChange: (text: string) => void;
32
35
  handleEmailChange: (text: string) => void;
33
36
  handlePasswordChange: (text: string) => void;
@@ -55,6 +58,24 @@ export function useRegisterForm(): UseRegisterFormResult {
55
58
  confirmPassword?: string;
56
59
  }>({});
57
60
 
61
+ const passwordRequirements = useMemo((): PasswordRequirements => {
62
+ if (!password) {
63
+ return {
64
+ hasMinLength: false,
65
+ hasUppercase: false,
66
+ hasLowercase: false,
67
+ hasNumber: false,
68
+ hasSpecialChar: false,
69
+ };
70
+ }
71
+ const result = validatePasswordForRegister(password, DEFAULT_PASSWORD_CONFIG);
72
+ return result.requirements;
73
+ }, [password]);
74
+
75
+ const passwordsMatch = useMemo(() => {
76
+ return password.length > 0 && password === confirmPassword;
77
+ }, [password, confirmPassword]);
78
+
58
79
  const handleDisplayNameChange = useCallback((text: string) => {
59
80
  setDisplayName(text);
60
81
  setFieldErrors((prev) => {
@@ -153,6 +174,8 @@ export function useRegisterForm(): UseRegisterFormResult {
153
174
  fieldErrors,
154
175
  localError,
155
176
  loading,
177
+ passwordRequirements,
178
+ passwordsMatch,
156
179
  handleDisplayNameChange,
157
180
  handleEmailChange,
158
181
  handlePasswordChange,