@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.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
+ });