@umituz/react-native-auth 2.5.5 → 2.5.6
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": "2.5.
|
|
3
|
+
"version": "2.5.6",
|
|
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
|
@@ -108,6 +108,9 @@ export type { UseAccountManagementReturn } from './presentation/hooks/useAccount
|
|
|
108
108
|
export { useProfileUpdate } from './presentation/hooks/useProfileUpdate';
|
|
109
109
|
export type { UseProfileUpdateReturn } from './presentation/hooks/useProfileUpdate';
|
|
110
110
|
|
|
111
|
+
export { useProfileEdit } from './presentation/hooks/useProfileEdit';
|
|
112
|
+
export type { UseProfileEditReturn, ProfileEditFormState } from './presentation/hooks/useProfileEdit';
|
|
113
|
+
|
|
111
114
|
export type { UserProfile, UpdateProfileParams } from './domain/entities/UserProfile';
|
|
112
115
|
|
|
113
116
|
// =============================================================================
|
|
@@ -118,6 +121,8 @@ export { LoginScreen } from './presentation/screens/LoginScreen';
|
|
|
118
121
|
export { RegisterScreen } from './presentation/screens/RegisterScreen';
|
|
119
122
|
export { AccountScreen } from './presentation/screens/AccountScreen';
|
|
120
123
|
export type { AccountScreenConfig, AccountScreenProps } from './presentation/screens/AccountScreen';
|
|
124
|
+
export { EditProfileScreen } from './presentation/screens/EditProfileScreen';
|
|
125
|
+
export type { EditProfileConfig, EditProfileScreenProps } from './presentation/screens/EditProfileScreen';
|
|
121
126
|
|
|
122
127
|
export { AuthNavigator } from './presentation/navigation/AuthNavigator';
|
|
123
128
|
export type {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useProfileEdit Hook
|
|
3
|
+
* Simple profile editing with form state management
|
|
4
|
+
* Apps provide image picker and backend update logic
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useCallback } from "react";
|
|
8
|
+
|
|
9
|
+
export interface ProfileEditFormState {
|
|
10
|
+
displayName: string;
|
|
11
|
+
email: string;
|
|
12
|
+
photoURL: string | null;
|
|
13
|
+
isModified: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface UseProfileEditReturn {
|
|
17
|
+
formState: ProfileEditFormState;
|
|
18
|
+
setDisplayName: (value: string) => void;
|
|
19
|
+
setEmail: (value: string) => void;
|
|
20
|
+
setPhotoURL: (value: string | null) => void;
|
|
21
|
+
resetForm: (initial: Partial<ProfileEditFormState>) => void;
|
|
22
|
+
validateForm: () => { isValid: boolean; errors: string[] };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const useProfileEdit = (
|
|
26
|
+
initialState: Partial<ProfileEditFormState> = {},
|
|
27
|
+
): UseProfileEditReturn => {
|
|
28
|
+
const [formState, setFormState] = useState<ProfileEditFormState>({
|
|
29
|
+
displayName: initialState.displayName || "",
|
|
30
|
+
email: initialState.email || "",
|
|
31
|
+
photoURL: initialState.photoURL || null,
|
|
32
|
+
isModified: false,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const setDisplayName = useCallback((value: string) => {
|
|
36
|
+
setFormState((prev) => ({ ...prev, displayName: value, isModified: true }));
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
const setEmail = useCallback((value: string) => {
|
|
40
|
+
setFormState((prev) => ({ ...prev, email: value, isModified: true }));
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
const setPhotoURL = useCallback((value: string | null) => {
|
|
44
|
+
setFormState((prev) => ({ ...prev, photoURL: value, isModified: true }));
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
const resetForm = useCallback((initial: Partial<ProfileEditFormState>) => {
|
|
48
|
+
setFormState({
|
|
49
|
+
displayName: initial.displayName || "",
|
|
50
|
+
email: initial.email || "",
|
|
51
|
+
photoURL: initial.photoURL || null,
|
|
52
|
+
isModified: false,
|
|
53
|
+
});
|
|
54
|
+
}, []);
|
|
55
|
+
|
|
56
|
+
const validateForm = useCallback((): {
|
|
57
|
+
isValid: boolean;
|
|
58
|
+
errors: string[];
|
|
59
|
+
} => {
|
|
60
|
+
const errors: string[] = [];
|
|
61
|
+
|
|
62
|
+
if (!formState.displayName.trim()) {
|
|
63
|
+
errors.push("Display name is required");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (formState.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formState.email)) {
|
|
67
|
+
errors.push("Invalid email format");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
isValid: errors.length === 0,
|
|
72
|
+
errors,
|
|
73
|
+
};
|
|
74
|
+
}, [formState]);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
formState,
|
|
78
|
+
setDisplayName,
|
|
79
|
+
setEmail,
|
|
80
|
+
setPhotoURL,
|
|
81
|
+
resetForm,
|
|
82
|
+
validateForm,
|
|
83
|
+
};
|
|
84
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit Profile Screen
|
|
3
|
+
* Pure UI for profile editing
|
|
4
|
+
* Business logic provided via props
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
import {
|
|
9
|
+
View,
|
|
10
|
+
Text,
|
|
11
|
+
TextInput,
|
|
12
|
+
TouchableOpacity,
|
|
13
|
+
ScrollView,
|
|
14
|
+
StyleSheet,
|
|
15
|
+
ActivityIndicator,
|
|
16
|
+
} from "react-native";
|
|
17
|
+
import { useAppDesignTokens } from "@umituz/react-native-design-system-theme";
|
|
18
|
+
|
|
19
|
+
export interface EditProfileConfig {
|
|
20
|
+
displayName: string;
|
|
21
|
+
email: string;
|
|
22
|
+
photoURL: string | null;
|
|
23
|
+
isLoading?: boolean;
|
|
24
|
+
isSaving?: boolean;
|
|
25
|
+
onChangeDisplayName: (value: string) => void;
|
|
26
|
+
onChangeEmail: (value: string) => void;
|
|
27
|
+
onChangePhoto?: () => void;
|
|
28
|
+
onSave: () => void;
|
|
29
|
+
onCancel?: () => void;
|
|
30
|
+
labels: {
|
|
31
|
+
title: string;
|
|
32
|
+
displayNameLabel: string;
|
|
33
|
+
displayNamePlaceholder: string;
|
|
34
|
+
emailLabel: string;
|
|
35
|
+
emailPlaceholder: string;
|
|
36
|
+
photoLabel: string;
|
|
37
|
+
changePhotoButton: string;
|
|
38
|
+
saveButton: string;
|
|
39
|
+
cancelButton: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface EditProfileScreenProps {
|
|
44
|
+
config: EditProfileConfig;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const EditProfileScreen: React.FC<EditProfileScreenProps> = ({
|
|
48
|
+
config,
|
|
49
|
+
}) => {
|
|
50
|
+
const tokens = useAppDesignTokens();
|
|
51
|
+
|
|
52
|
+
if (config.isLoading) {
|
|
53
|
+
return (
|
|
54
|
+
<View style={[styles.loading, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
55
|
+
<ActivityIndicator size="large" color={tokens.colors.primary} />
|
|
56
|
+
</View>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<ScrollView
|
|
62
|
+
style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}
|
|
63
|
+
contentContainerStyle={styles.content}
|
|
64
|
+
>
|
|
65
|
+
<Text style={[styles.title, { color: tokens.colors.text }]}>
|
|
66
|
+
{config.labels.title}
|
|
67
|
+
</Text>
|
|
68
|
+
|
|
69
|
+
{/* Display Name */}
|
|
70
|
+
<View style={styles.field}>
|
|
71
|
+
<Text style={[styles.label, { color: tokens.colors.textSecondary }]}>
|
|
72
|
+
{config.labels.displayNameLabel}
|
|
73
|
+
</Text>
|
|
74
|
+
<TextInput
|
|
75
|
+
style={[
|
|
76
|
+
styles.input,
|
|
77
|
+
{
|
|
78
|
+
backgroundColor: tokens.colors.surface,
|
|
79
|
+
color: tokens.colors.text,
|
|
80
|
+
borderColor: tokens.colors.border,
|
|
81
|
+
},
|
|
82
|
+
]}
|
|
83
|
+
value={config.displayName}
|
|
84
|
+
onChangeText={config.onChangeDisplayName}
|
|
85
|
+
placeholder={config.labels.displayNamePlaceholder}
|
|
86
|
+
placeholderTextColor={tokens.colors.textTertiary}
|
|
87
|
+
/>
|
|
88
|
+
</View>
|
|
89
|
+
|
|
90
|
+
{/* Email */}
|
|
91
|
+
<View style={styles.field}>
|
|
92
|
+
<Text style={[styles.label, { color: tokens.colors.textSecondary }]}>
|
|
93
|
+
{config.labels.emailLabel}
|
|
94
|
+
</Text>
|
|
95
|
+
<TextInput
|
|
96
|
+
style={[
|
|
97
|
+
styles.input,
|
|
98
|
+
{
|
|
99
|
+
backgroundColor: tokens.colors.surface,
|
|
100
|
+
color: tokens.colors.text,
|
|
101
|
+
borderColor: tokens.colors.border,
|
|
102
|
+
},
|
|
103
|
+
]}
|
|
104
|
+
value={config.email}
|
|
105
|
+
onChangeText={config.onChangeEmail}
|
|
106
|
+
placeholder={config.labels.emailPlaceholder}
|
|
107
|
+
placeholderTextColor={tokens.colors.textTertiary}
|
|
108
|
+
keyboardType="email-address"
|
|
109
|
+
autoCapitalize="none"
|
|
110
|
+
/>
|
|
111
|
+
</View>
|
|
112
|
+
|
|
113
|
+
{/* Photo */}
|
|
114
|
+
{config.onChangePhoto && (
|
|
115
|
+
<View style={styles.field}>
|
|
116
|
+
<Text style={[styles.label, { color: tokens.colors.textSecondary }]}>
|
|
117
|
+
{config.labels.photoLabel}
|
|
118
|
+
</Text>
|
|
119
|
+
<TouchableOpacity
|
|
120
|
+
style={[
|
|
121
|
+
styles.photoButton,
|
|
122
|
+
{ backgroundColor: tokens.colors.surface, borderColor: tokens.colors.border },
|
|
123
|
+
]}
|
|
124
|
+
onPress={config.onChangePhoto}
|
|
125
|
+
>
|
|
126
|
+
<Text style={[styles.photoButtonText, { color: tokens.colors.primary }]}>
|
|
127
|
+
{config.labels.changePhotoButton}
|
|
128
|
+
</Text>
|
|
129
|
+
</TouchableOpacity>
|
|
130
|
+
</View>
|
|
131
|
+
)}
|
|
132
|
+
|
|
133
|
+
{/* Actions */}
|
|
134
|
+
<View style={styles.actions}>
|
|
135
|
+
<TouchableOpacity
|
|
136
|
+
style={[styles.saveButton, { backgroundColor: tokens.colors.primary }]}
|
|
137
|
+
onPress={config.onSave}
|
|
138
|
+
disabled={config.isSaving}
|
|
139
|
+
>
|
|
140
|
+
{config.isSaving ? (
|
|
141
|
+
<ActivityIndicator size="small" color={tokens.colors.onPrimary} />
|
|
142
|
+
) : (
|
|
143
|
+
<Text style={[styles.saveButtonText, { color: tokens.colors.onPrimary }]}>
|
|
144
|
+
{config.labels.saveButton}
|
|
145
|
+
</Text>
|
|
146
|
+
)}
|
|
147
|
+
</TouchableOpacity>
|
|
148
|
+
|
|
149
|
+
{config.onCancel && (
|
|
150
|
+
<TouchableOpacity
|
|
151
|
+
style={[
|
|
152
|
+
styles.cancelButton,
|
|
153
|
+
{ backgroundColor: tokens.colors.surface, borderColor: tokens.colors.border },
|
|
154
|
+
]}
|
|
155
|
+
onPress={config.onCancel}
|
|
156
|
+
disabled={config.isSaving}
|
|
157
|
+
>
|
|
158
|
+
<Text style={[styles.cancelButtonText, { color: tokens.colors.text }]}>
|
|
159
|
+
{config.labels.cancelButton}
|
|
160
|
+
</Text>
|
|
161
|
+
</TouchableOpacity>
|
|
162
|
+
)}
|
|
163
|
+
</View>
|
|
164
|
+
</ScrollView>
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const styles = StyleSheet.create({
|
|
169
|
+
container: {
|
|
170
|
+
flex: 1,
|
|
171
|
+
},
|
|
172
|
+
content: {
|
|
173
|
+
padding: 16,
|
|
174
|
+
},
|
|
175
|
+
loading: {
|
|
176
|
+
flex: 1,
|
|
177
|
+
justifyContent: "center",
|
|
178
|
+
alignItems: "center",
|
|
179
|
+
},
|
|
180
|
+
title: {
|
|
181
|
+
fontSize: 24,
|
|
182
|
+
fontWeight: "600",
|
|
183
|
+
marginBottom: 24,
|
|
184
|
+
},
|
|
185
|
+
field: {
|
|
186
|
+
marginBottom: 20,
|
|
187
|
+
},
|
|
188
|
+
label: {
|
|
189
|
+
fontSize: 14,
|
|
190
|
+
fontWeight: "500",
|
|
191
|
+
marginBottom: 8,
|
|
192
|
+
},
|
|
193
|
+
input: {
|
|
194
|
+
borderWidth: 1,
|
|
195
|
+
borderRadius: 8,
|
|
196
|
+
padding: 12,
|
|
197
|
+
fontSize: 16,
|
|
198
|
+
},
|
|
199
|
+
photoButton: {
|
|
200
|
+
borderWidth: 1,
|
|
201
|
+
borderRadius: 8,
|
|
202
|
+
padding: 12,
|
|
203
|
+
alignItems: "center",
|
|
204
|
+
},
|
|
205
|
+
photoButtonText: {
|
|
206
|
+
fontSize: 14,
|
|
207
|
+
fontWeight: "500",
|
|
208
|
+
},
|
|
209
|
+
actions: {
|
|
210
|
+
marginTop: 32,
|
|
211
|
+
gap: 12,
|
|
212
|
+
},
|
|
213
|
+
saveButton: {
|
|
214
|
+
padding: 16,
|
|
215
|
+
borderRadius: 8,
|
|
216
|
+
alignItems: "center",
|
|
217
|
+
},
|
|
218
|
+
saveButtonText: {
|
|
219
|
+
fontSize: 16,
|
|
220
|
+
fontWeight: "600",
|
|
221
|
+
},
|
|
222
|
+
cancelButton: {
|
|
223
|
+
padding: 16,
|
|
224
|
+
borderRadius: 8,
|
|
225
|
+
alignItems: "center",
|
|
226
|
+
borderWidth: 1,
|
|
227
|
+
},
|
|
228
|
+
cancelButtonText: {
|
|
229
|
+
fontSize: 16,
|
|
230
|
+
fontWeight: "500",
|
|
231
|
+
},
|
|
232
|
+
});
|