@umituz/react-native-auth 3.6.6 → 3.6.8

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": "3.6.6",
3
+ "version": "3.6.8",
4
4
  "description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design with dependency injection, configurable validation, and comprehensive error handling.",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
package/src/index.ts CHANGED
@@ -161,6 +161,8 @@ export { AccountScreen } from './presentation/screens/AccountScreen';
161
161
  export type { AccountScreenConfig, AccountScreenProps } from './presentation/screens/AccountScreen';
162
162
  export { EditProfileScreen } from './presentation/screens/EditProfileScreen';
163
163
  export type { EditProfileConfig, EditProfileScreenProps } from './presentation/screens/EditProfileScreen';
164
+ export { ChangePasswordScreen } from './presentation/screens/ChangePasswordScreen';
165
+ export type { ChangePasswordScreenProps } from './presentation/screens/ChangePasswordScreen';
164
166
  export { AuthNavigator } from './presentation/navigation/AuthNavigator';
165
167
  export type {
166
168
  AuthStackParamList,
@@ -0,0 +1,246 @@
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
+ import React, { useState } from "react";
12
+ import { View, StyleSheet, Alert } from "react-native";
13
+ import {
14
+ ScreenLayout,
15
+ AtomicInput,
16
+ AtomicButton,
17
+ AtomicText,
18
+ useAppDesignTokens,
19
+ type AtomicInputProps,
20
+ } from "@umituz/react-native-design-system";
21
+ import { useTranslation } from "@umituz/react-native-localization";
22
+ import {
23
+ updateUserPassword,
24
+ reauthenticateWithPassword,
25
+ getCurrentUserFromGlobal,
26
+ getFirebaseAuth,
27
+ } from "@umituz/react-native-firebase";
28
+
29
+ export interface ChangePasswordScreenProps {
30
+ onSuccess?: () => void;
31
+ onCancel?: () => void;
32
+ }
33
+
34
+ export const ChangePasswordScreen: React.FC<ChangePasswordScreenProps> = ({
35
+ onSuccess,
36
+ onCancel,
37
+ }) => {
38
+ const { t } = useTranslation();
39
+ const tokens = useAppDesignTokens();
40
+ const auth = getFirebaseAuth();
41
+
42
+ const [currentPassword, setCurrentPassword] = useState("");
43
+ const [newPassword, setNewPassword] = useState("");
44
+ const [confirmPassword, setConfirmPassword] = useState("");
45
+ const [loading, setLoading] = useState(false);
46
+ const [error, setError] = useState<string | null>(null);
47
+
48
+ /* Removed manual visibility states */
49
+ /* const [showCurrentPassword, setShowCurrentPassword] = useState(false); */
50
+ /* const [showNewPassword, setShowNewPassword] = useState(false); */
51
+ /* const [showConfirmPassword, setShowConfirmPassword] = useState(false); */
52
+
53
+ // Validation state
54
+ const isLengthValid = newPassword.length >= 8;
55
+ const hasUppercase = /[A-Z]/.test(newPassword);
56
+ const hasLowercase = /[a-z]/.test(newPassword);
57
+ const hasNumber = /[0-9]/.test(newPassword);
58
+ const hasSpecialChar = /[!@#$%^&*]/.test(newPassword);
59
+ const passwordsMatch = newPassword === confirmPassword && newPassword !== "";
60
+
61
+ const isValid =
62
+ isLengthValid &&
63
+ hasUppercase &&
64
+ hasLowercase &&
65
+ hasNumber &&
66
+ hasSpecialChar &&
67
+ passwordsMatch &&
68
+ currentPassword.length > 0;
69
+
70
+ const handleChangePassword = async () => {
71
+ if (!isValid) {
72
+ Alert.alert(t("common.error"), t("auth.passwordChange.fillAllFields"));
73
+ return;
74
+ }
75
+
76
+ const user = getCurrentUserFromGlobal();
77
+ if (!user) {
78
+ setError(t("auth.errors.unauthorized"));
79
+ return;
80
+ }
81
+
82
+ setLoading(true);
83
+ setError(null);
84
+
85
+ try {
86
+ // 1. Re-authenticate
87
+ const reauthResult = await reauthenticateWithPassword(user, currentPassword);
88
+ if (!reauthResult.success) {
89
+ setError(reauthResult.error?.message || t("auth.alerts.error.signInFailed"));
90
+ setLoading(false);
91
+ return;
92
+ }
93
+
94
+ // 2. Update Password
95
+ const updateResult = await updateUserPassword(user, newPassword);
96
+ if (updateResult.success) {
97
+ Alert.alert(t("common.success"), t("auth.passwordChange.success"), [
98
+ { text: "OK", onPress: onSuccess }
99
+ ]);
100
+ if (onSuccess) onSuccess();
101
+ } else {
102
+ setError(updateResult.error?.message || t("auth.passwordChange.error"));
103
+ }
104
+ } catch (e: any) {
105
+ setError(e.message || t("auth.passwordChange.error"));
106
+ } finally {
107
+ setLoading(false);
108
+ }
109
+ };
110
+
111
+ const RequirementsList = () => (
112
+ <View style={styles.requirementsContainer}>
113
+ <AtomicText variant="labelMedium" style={{ color: tokens.colors.textSecondary, marginBottom: 8 }}>
114
+ {t("auth.passwordChange.requirements")}
115
+ </AtomicText>
116
+ <RequirementItem label={t("auth.passwordChange.minLength")} met={isLengthValid} />
117
+ <RequirementItem label={t("auth.passwordChange.uppercase")} met={hasUppercase} />
118
+ <RequirementItem label={t("auth.passwordChange.lowercase")} met={hasLowercase} />
119
+ <RequirementItem label={t("auth.passwordChange.number")} met={hasNumber} />
120
+ <RequirementItem label={t("auth.passwordChange.specialChar")} met={hasSpecialChar} />
121
+ <RequirementItem label={t("auth.passwordChange.passwordsMatch")} met={passwordsMatch} />
122
+ </View>
123
+ );
124
+
125
+ const RequirementItem = ({ label, met }: { label: string; met: boolean }) => (
126
+ <View style={styles.requirementItem}>
127
+ <View
128
+ style={[
129
+ styles.requirementBullet,
130
+ { backgroundColor: met ? tokens.colors.success : tokens.colors.border },
131
+ ]}
132
+ />
133
+ <AtomicText
134
+ variant="bodySmall"
135
+ style={{ color: met ? tokens.colors.textPrimary : tokens.colors.textSecondary }}
136
+ >
137
+ {label}
138
+ </AtomicText>
139
+ </View>
140
+ );
141
+
142
+ return (
143
+ <ScreenLayout
144
+ scrollable
145
+ title={t("auth.passwordChange.title")}
146
+ backgroundColor={tokens.colors.backgroundPrimary}
147
+ edges={["bottom"]}
148
+ >
149
+ <View style={styles.content}>
150
+ <AtomicText variant="bodyMedium" style={{ color: tokens.colors.textSecondary, marginBottom: 24 }}>
151
+ {t("auth.passwordChange.description")}
152
+ </AtomicText>
153
+
154
+ <View style={styles.form}>
155
+ {/* Current Password */}
156
+ <AtomicInput
157
+ label={t("auth.passwordChange.currentPassword")}
158
+ placeholder={t("auth.passwordChange.enterCurrentPassword")}
159
+ value={currentPassword}
160
+ onChangeText={setCurrentPassword}
161
+ secureTextEntry
162
+ showPasswordToggle
163
+ variant="filled"
164
+ />
165
+
166
+ {/* New Password */}
167
+ <AtomicInput
168
+ label={t("auth.passwordChange.newPassword")}
169
+ placeholder={t("auth.passwordChange.enterNewPassword")}
170
+ value={newPassword}
171
+ onChangeText={setNewPassword}
172
+ secureTextEntry
173
+ showPasswordToggle
174
+ variant="filled"
175
+ />
176
+
177
+ {/* Confirm Password */}
178
+ <AtomicInput
179
+ label={t("auth.passwordChange.confirmPassword")}
180
+ placeholder={t("auth.passwordChange.enterConfirmPassword")}
181
+ value={confirmPassword}
182
+ onChangeText={setConfirmPassword}
183
+ secureTextEntry
184
+ showPasswordToggle
185
+ variant="filled"
186
+ />
187
+
188
+ <RequirementsList />
189
+
190
+ {error && (
191
+ <AtomicText style={{ color: tokens.colors.error, marginTop: 16 }}>
192
+ {error}
193
+ </AtomicText>
194
+ )}
195
+
196
+ <View style={styles.actions}>
197
+ <AtomicButton
198
+ title={t("common.cancel")}
199
+ onPress={onCancel || (() => {})}
200
+ variant="outline"
201
+ style={{ flex: 1 }}
202
+ />
203
+ <AtomicButton
204
+ title={loading ? t("auth.passwordChange.changing") : t("auth.passwordChange.changePassword")}
205
+ onPress={handleChangePassword}
206
+ loading={loading}
207
+ disabled={!isValid || loading}
208
+ style={{ flex: 1 }}
209
+ />
210
+ </View>
211
+ </View>
212
+ </View>
213
+ </ScreenLayout>
214
+ );
215
+ };
216
+
217
+ const styles = StyleSheet.create({
218
+ content: {
219
+ padding: 16,
220
+ },
221
+ form: {
222
+ gap: 16,
223
+ },
224
+ requirementsContainer: {
225
+ marginTop: 8,
226
+ padding: 12,
227
+ borderRadius: 8,
228
+ backgroundColor: "rgba(0,0,0,0.05)",
229
+ },
230
+ requirementItem: {
231
+ flexDirection: "row",
232
+ alignItems: "center",
233
+ marginBottom: 4,
234
+ },
235
+ requirementBullet: {
236
+ width: 6,
237
+ height: 6,
238
+ borderRadius: 3,
239
+ marginRight: 8,
240
+ },
241
+ actions: {
242
+ flexDirection: "row",
243
+ gap: 12,
244
+ marginTop: 24,
245
+ },
246
+ });