@oxyhq/services 5.1.29 → 5.1.30
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/README.md +71 -24
- package/UI_COMPONENTS.md +48 -0
- package/lib/commonjs/core/index.js +17 -4
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/index.js +0 -3
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/node/index.js +52 -0
- package/lib/commonjs/node/index.js.map +1 -0
- package/lib/commonjs/ui/index.js +9 -1
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/navigation/OxyRouter.js +5 -0
- package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountCenterScreen.js +6 -3
- package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +638 -0
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -0
- package/lib/module/core/index.js +17 -4
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/index.js +0 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/node/index.js +20 -0
- package/lib/module/node/index.js.map +1 -0
- package/lib/module/ui/index.js +1 -0
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/navigation/OxyRouter.js +5 -0
- package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/module/ui/screens/AccountCenterScreen.js +6 -3
- package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +632 -0
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -0
- package/lib/typescript/__tests__/ui/screens/AccountSettingsScreen.test.d.ts +2 -0
- package/lib/typescript/__tests__/ui/screens/AccountSettingsScreen.test.d.ts.map +1 -0
- package/lib/typescript/core/index.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +0 -3
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/node/index.d.ts +10 -0
- package/lib/typescript/node/index.d.ts.map +1 -0
- package/lib/typescript/ui/index.d.ts +1 -0
- package/lib/typescript/ui/index.d.ts.map +1 -1
- package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts +8 -0
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -0
- package/package.json +5 -22
- package/src/__tests__/ui/screens/AccountSettingsScreen.test.tsx +112 -0
- package/src/core/index.ts +16 -4
- package/src/index.ts +0 -3
- package/src/node/index.ts +17 -0
- package/src/ui/index.ts +1 -0
- package/src/ui/navigation/OxyRouter.tsx +5 -0
- package/src/ui/screens/AccountCenterScreen.tsx +3 -2
- package/src/ui/screens/AccountSettingsScreen.tsx +616 -0
- package/lib/commonjs/backend.js +0 -44
- package/lib/commonjs/backend.js.map +0 -1
- package/lib/commonjs/core-only.js +0 -44
- package/lib/commonjs/core-only.js.map +0 -1
- package/lib/commonjs/test-backend.js +0 -21
- package/lib/commonjs/test-backend.js.map +0 -1
- package/lib/module/backend.js +0 -17
- package/lib/module/backend.js.map +0 -1
- package/lib/module/core-only.js +0 -17
- package/lib/module/core-only.js.map +0 -1
- package/lib/module/test-backend.js +0 -18
- package/lib/module/test-backend.js.map +0 -1
- package/lib/typescript/backend.d.ts +0 -9
- package/lib/typescript/backend.d.ts.map +0 -1
- package/lib/typescript/core-only.d.ts +0 -9
- package/lib/typescript/core-only.d.ts.map +0 -1
- package/lib/typescript/test-backend.d.ts +0 -8
- package/lib/typescript/test-backend.d.ts.map +0 -1
- package/src/backend.ts +0 -14
- package/src/core-only.ts +0 -14
- package/src/test-backend.ts +0 -17
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
TextInput,
|
|
6
|
+
TouchableOpacity,
|
|
7
|
+
StyleSheet,
|
|
8
|
+
ActivityIndicator,
|
|
9
|
+
ScrollView,
|
|
10
|
+
Alert,
|
|
11
|
+
Platform,
|
|
12
|
+
Switch,
|
|
13
|
+
} from 'react-native';
|
|
14
|
+
import { BaseScreenProps } from '../navigation/types';
|
|
15
|
+
import { useOxy } from '../context/OxyContext';
|
|
16
|
+
import Avatar from '../components/Avatar';
|
|
17
|
+
|
|
18
|
+
interface AccountSettingsScreenProps extends BaseScreenProps {
|
|
19
|
+
activeTab?: 'profile' | 'password' | 'notifications';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const AccountSettingsScreen: React.FC<AccountSettingsScreenProps> = ({
|
|
23
|
+
goBack,
|
|
24
|
+
theme,
|
|
25
|
+
activeTab = 'profile',
|
|
26
|
+
}) => {
|
|
27
|
+
const { user, oxyServices, isLoading: authLoading } = useOxy();
|
|
28
|
+
const [currentTab, setCurrentTab] = useState<'profile' | 'password' | 'notifications'>(activeTab);
|
|
29
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
30
|
+
const [errorMessage, setErrorMessage] = useState('');
|
|
31
|
+
const [successMessage, setSuccessMessage] = useState('');
|
|
32
|
+
|
|
33
|
+
// Profile form state
|
|
34
|
+
const [username, setUsername] = useState('');
|
|
35
|
+
const [email, setEmail] = useState('');
|
|
36
|
+
const [bio, setBio] = useState('');
|
|
37
|
+
const [avatarUrl, setAvatarUrl] = useState('');
|
|
38
|
+
|
|
39
|
+
// Password form state
|
|
40
|
+
const [currentPassword, setCurrentPassword] = useState('');
|
|
41
|
+
const [newPassword, setNewPassword] = useState('');
|
|
42
|
+
const [confirmPassword, setConfirmPassword] = useState('');
|
|
43
|
+
|
|
44
|
+
// Notification preferences
|
|
45
|
+
const [emailNotifications, setEmailNotifications] = useState(true);
|
|
46
|
+
const [pushNotifications, setPushNotifications] = useState(true);
|
|
47
|
+
|
|
48
|
+
// Theme and styling
|
|
49
|
+
const isDarkTheme = theme === 'dark';
|
|
50
|
+
const textColor = isDarkTheme ? '#FFFFFF' : '#000000';
|
|
51
|
+
const backgroundColor = isDarkTheme ? '#121212' : '#FFFFFF';
|
|
52
|
+
const secondaryBackgroundColor = isDarkTheme ? '#222222' : '#F5F5F5';
|
|
53
|
+
const inputBackgroundColor = isDarkTheme ? '#333333' : '#F5F5F5';
|
|
54
|
+
const borderColor = isDarkTheme ? '#444444' : '#E0E0E0';
|
|
55
|
+
const primaryColor = '#0066CC';
|
|
56
|
+
const placeholderColor = isDarkTheme ? '#AAAAAA' : '#999999';
|
|
57
|
+
|
|
58
|
+
// Load user data
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (user) {
|
|
61
|
+
setUsername(user.username || '');
|
|
62
|
+
setEmail(user.email || '');
|
|
63
|
+
setBio(user.bio || '');
|
|
64
|
+
setAvatarUrl(user.avatar?.url || '');
|
|
65
|
+
}
|
|
66
|
+
}, [user]);
|
|
67
|
+
|
|
68
|
+
const validateEmail = (email: string) => {
|
|
69
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
70
|
+
return emailRegex.test(email);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const handleSaveProfile = async () => {
|
|
74
|
+
// Validate inputs
|
|
75
|
+
if (!username) {
|
|
76
|
+
setErrorMessage('Username is required');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (email && !validateEmail(email)) {
|
|
81
|
+
setErrorMessage('Please enter a valid email address');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
setIsLoading(true);
|
|
87
|
+
setErrorMessage('');
|
|
88
|
+
setSuccessMessage('');
|
|
89
|
+
|
|
90
|
+
// Prepare updates object
|
|
91
|
+
const updates: Record<string, any> = {
|
|
92
|
+
username,
|
|
93
|
+
bio,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (email) {
|
|
97
|
+
updates.email = email;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Only include avatar if it's been changed
|
|
101
|
+
if (avatarUrl !== user?.avatar?.url) {
|
|
102
|
+
updates.avatar = { url: avatarUrl };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Call API to update user
|
|
106
|
+
await oxyServices.updateUser(user!.id, updates);
|
|
107
|
+
setSuccessMessage('Profile updated successfully');
|
|
108
|
+
} catch (error: any) {
|
|
109
|
+
setErrorMessage(error.message || 'Failed to update profile');
|
|
110
|
+
} finally {
|
|
111
|
+
setIsLoading(false);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const handleChangePassword = async () => {
|
|
116
|
+
// Validate inputs
|
|
117
|
+
if (!currentPassword || !newPassword || !confirmPassword) {
|
|
118
|
+
setErrorMessage('Please fill in all password fields');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (newPassword !== confirmPassword) {
|
|
123
|
+
setErrorMessage('New passwords do not match');
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (newPassword.length < 8) {
|
|
128
|
+
setErrorMessage('Password must be at least 8 characters long');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
setIsLoading(true);
|
|
134
|
+
setErrorMessage('');
|
|
135
|
+
setSuccessMessage('');
|
|
136
|
+
|
|
137
|
+
// Call API to update password
|
|
138
|
+
await oxyServices.updateUser(user!.id, {
|
|
139
|
+
currentPassword,
|
|
140
|
+
password: newPassword,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Clear form fields after successful update
|
|
144
|
+
setCurrentPassword('');
|
|
145
|
+
setNewPassword('');
|
|
146
|
+
setConfirmPassword('');
|
|
147
|
+
setSuccessMessage('Password updated successfully');
|
|
148
|
+
} catch (error: any) {
|
|
149
|
+
setErrorMessage(error.message || 'Failed to update password');
|
|
150
|
+
} finally {
|
|
151
|
+
setIsLoading(false);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const handleSaveNotifications = async () => {
|
|
156
|
+
try {
|
|
157
|
+
setIsLoading(true);
|
|
158
|
+
setErrorMessage('');
|
|
159
|
+
setSuccessMessage('');
|
|
160
|
+
|
|
161
|
+
// Call API to update notification preferences
|
|
162
|
+
await oxyServices.updateUser(user!.id, {
|
|
163
|
+
notificationPreferences: {
|
|
164
|
+
email: emailNotifications,
|
|
165
|
+
push: pushNotifications,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
setSuccessMessage('Notification preferences updated successfully');
|
|
169
|
+
} catch (error: any) {
|
|
170
|
+
setErrorMessage(error.message || 'Failed to update notification preferences');
|
|
171
|
+
} finally {
|
|
172
|
+
setIsLoading(false);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const handleAvatarUpdate = () => {
|
|
177
|
+
// In a real app, this would open an image picker
|
|
178
|
+
// For now, we'll use a mock URL to demonstrate the concept
|
|
179
|
+
Alert.alert(
|
|
180
|
+
'Update Avatar',
|
|
181
|
+
'This would open an image picker in a real app. For now, we\'ll use a mock URL.',
|
|
182
|
+
[
|
|
183
|
+
{
|
|
184
|
+
text: 'Cancel',
|
|
185
|
+
style: 'cancel',
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
text: 'Use Mock URL',
|
|
189
|
+
onPress: () => {
|
|
190
|
+
const mockUrl = `https://ui-avatars.com/api/?name=${username}&background=random`;
|
|
191
|
+
setAvatarUrl(mockUrl);
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
]
|
|
195
|
+
);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
if (authLoading || !user) {
|
|
199
|
+
return (
|
|
200
|
+
<View style={[styles.container, { backgroundColor, justifyContent: 'center' }]}>
|
|
201
|
+
<ActivityIndicator size="large" color={primaryColor} />
|
|
202
|
+
</View>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const renderProfileTab = () => (
|
|
207
|
+
<View style={styles.tabContent}>
|
|
208
|
+
<View style={styles.avatarSection}>
|
|
209
|
+
<Avatar
|
|
210
|
+
uri={avatarUrl}
|
|
211
|
+
name={username}
|
|
212
|
+
size={100}
|
|
213
|
+
theme={theme}
|
|
214
|
+
/>
|
|
215
|
+
<TouchableOpacity
|
|
216
|
+
style={[styles.changeAvatarButton, { backgroundColor: primaryColor }]}
|
|
217
|
+
onPress={handleAvatarUpdate}
|
|
218
|
+
>
|
|
219
|
+
<Text style={styles.changeAvatarText}>Change Avatar</Text>
|
|
220
|
+
</TouchableOpacity>
|
|
221
|
+
</View>
|
|
222
|
+
|
|
223
|
+
<View style={styles.inputContainer}>
|
|
224
|
+
<Text style={[styles.label, { color: textColor }]}>Username</Text>
|
|
225
|
+
<TextInput
|
|
226
|
+
style={[
|
|
227
|
+
styles.input,
|
|
228
|
+
{ backgroundColor: inputBackgroundColor, borderColor, color: textColor }
|
|
229
|
+
]}
|
|
230
|
+
placeholder="Enter your username"
|
|
231
|
+
placeholderTextColor={placeholderColor}
|
|
232
|
+
value={username}
|
|
233
|
+
onChangeText={setUsername}
|
|
234
|
+
testID="username-input"
|
|
235
|
+
/>
|
|
236
|
+
</View>
|
|
237
|
+
|
|
238
|
+
<View style={styles.inputContainer}>
|
|
239
|
+
<Text style={[styles.label, { color: textColor }]}>Email</Text>
|
|
240
|
+
<TextInput
|
|
241
|
+
style={[
|
|
242
|
+
styles.input,
|
|
243
|
+
{ backgroundColor: inputBackgroundColor, borderColor, color: textColor }
|
|
244
|
+
]}
|
|
245
|
+
placeholder="Enter your email"
|
|
246
|
+
placeholderTextColor={placeholderColor}
|
|
247
|
+
value={email}
|
|
248
|
+
onChangeText={setEmail}
|
|
249
|
+
keyboardType="email-address"
|
|
250
|
+
testID="email-input"
|
|
251
|
+
/>
|
|
252
|
+
</View>
|
|
253
|
+
|
|
254
|
+
<View style={styles.inputContainer}>
|
|
255
|
+
<Text style={[styles.label, { color: textColor }]}>Bio</Text>
|
|
256
|
+
<TextInput
|
|
257
|
+
style={[
|
|
258
|
+
styles.textArea,
|
|
259
|
+
{ backgroundColor: inputBackgroundColor, borderColor, color: textColor }
|
|
260
|
+
]}
|
|
261
|
+
placeholder="Tell us about yourself"
|
|
262
|
+
placeholderTextColor={placeholderColor}
|
|
263
|
+
value={bio}
|
|
264
|
+
onChangeText={setBio}
|
|
265
|
+
multiline
|
|
266
|
+
numberOfLines={4}
|
|
267
|
+
testID="bio-input"
|
|
268
|
+
/>
|
|
269
|
+
</View>
|
|
270
|
+
|
|
271
|
+
<TouchableOpacity
|
|
272
|
+
style={[styles.saveButton, { backgroundColor: primaryColor, opacity: isLoading ? 0.7 : 1 }]}
|
|
273
|
+
onPress={handleSaveProfile}
|
|
274
|
+
disabled={isLoading}
|
|
275
|
+
testID="save-profile-button"
|
|
276
|
+
>
|
|
277
|
+
{isLoading ? (
|
|
278
|
+
<ActivityIndicator color="#FFFFFF" size="small" />
|
|
279
|
+
) : (
|
|
280
|
+
<Text style={styles.saveButtonText}>Save Profile</Text>
|
|
281
|
+
)}
|
|
282
|
+
</TouchableOpacity>
|
|
283
|
+
</View>
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
const renderPasswordTab = () => (
|
|
287
|
+
<View style={styles.tabContent}>
|
|
288
|
+
<View style={styles.inputContainer}>
|
|
289
|
+
<Text style={[styles.label, { color: textColor }]}>Current Password</Text>
|
|
290
|
+
<TextInput
|
|
291
|
+
style={[
|
|
292
|
+
styles.input,
|
|
293
|
+
{ backgroundColor: inputBackgroundColor, borderColor, color: textColor }
|
|
294
|
+
]}
|
|
295
|
+
placeholder="Enter your current password"
|
|
296
|
+
placeholderTextColor={placeholderColor}
|
|
297
|
+
value={currentPassword}
|
|
298
|
+
onChangeText={setCurrentPassword}
|
|
299
|
+
secureTextEntry
|
|
300
|
+
testID="current-password-input"
|
|
301
|
+
/>
|
|
302
|
+
</View>
|
|
303
|
+
|
|
304
|
+
<View style={styles.inputContainer}>
|
|
305
|
+
<Text style={[styles.label, { color: textColor }]}>New Password</Text>
|
|
306
|
+
<TextInput
|
|
307
|
+
style={[
|
|
308
|
+
styles.input,
|
|
309
|
+
{ backgroundColor: inputBackgroundColor, borderColor, color: textColor }
|
|
310
|
+
]}
|
|
311
|
+
placeholder="Enter your new password"
|
|
312
|
+
placeholderTextColor={placeholderColor}
|
|
313
|
+
value={newPassword}
|
|
314
|
+
onChangeText={setNewPassword}
|
|
315
|
+
secureTextEntry
|
|
316
|
+
testID="new-password-input"
|
|
317
|
+
/>
|
|
318
|
+
<Text style={[styles.passwordHint, { color: isDarkTheme ? '#AAAAAA' : '#666666' }]}>
|
|
319
|
+
Password must be at least 8 characters long
|
|
320
|
+
</Text>
|
|
321
|
+
</View>
|
|
322
|
+
|
|
323
|
+
<View style={styles.inputContainer}>
|
|
324
|
+
<Text style={[styles.label, { color: textColor }]}>Confirm New Password</Text>
|
|
325
|
+
<TextInput
|
|
326
|
+
style={[
|
|
327
|
+
styles.input,
|
|
328
|
+
{ backgroundColor: inputBackgroundColor, borderColor, color: textColor }
|
|
329
|
+
]}
|
|
330
|
+
placeholder="Confirm your new password"
|
|
331
|
+
placeholderTextColor={placeholderColor}
|
|
332
|
+
value={confirmPassword}
|
|
333
|
+
onChangeText={setConfirmPassword}
|
|
334
|
+
secureTextEntry
|
|
335
|
+
testID="confirm-password-input"
|
|
336
|
+
/>
|
|
337
|
+
</View>
|
|
338
|
+
|
|
339
|
+
<TouchableOpacity
|
|
340
|
+
style={[styles.saveButton, { backgroundColor: primaryColor, opacity: isLoading ? 0.7 : 1 }]}
|
|
341
|
+
onPress={handleChangePassword}
|
|
342
|
+
disabled={isLoading}
|
|
343
|
+
testID="change-password-button"
|
|
344
|
+
>
|
|
345
|
+
{isLoading ? (
|
|
346
|
+
<ActivityIndicator color="#FFFFFF" size="small" />
|
|
347
|
+
) : (
|
|
348
|
+
<Text style={styles.saveButtonText}>Change Password</Text>
|
|
349
|
+
)}
|
|
350
|
+
</TouchableOpacity>
|
|
351
|
+
</View>
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const renderNotificationsTab = () => (
|
|
355
|
+
<View style={styles.tabContent}>
|
|
356
|
+
<View style={styles.settingRow}>
|
|
357
|
+
<Text style={[styles.settingLabel, { color: textColor }]}>Email Notifications</Text>
|
|
358
|
+
<Switch
|
|
359
|
+
value={emailNotifications}
|
|
360
|
+
onValueChange={setEmailNotifications}
|
|
361
|
+
trackColor={{ false: '#767577', true: primaryColor }}
|
|
362
|
+
thumbColor="#f4f3f4"
|
|
363
|
+
testID="email-notifications-switch"
|
|
364
|
+
/>
|
|
365
|
+
</View>
|
|
366
|
+
|
|
367
|
+
<View style={styles.settingRow}>
|
|
368
|
+
<Text style={[styles.settingLabel, { color: textColor }]}>Push Notifications</Text>
|
|
369
|
+
<Switch
|
|
370
|
+
value={pushNotifications}
|
|
371
|
+
onValueChange={setPushNotifications}
|
|
372
|
+
trackColor={{ false: '#767577', true: primaryColor }}
|
|
373
|
+
thumbColor="#f4f3f4"
|
|
374
|
+
testID="push-notifications-switch"
|
|
375
|
+
/>
|
|
376
|
+
</View>
|
|
377
|
+
|
|
378
|
+
<TouchableOpacity
|
|
379
|
+
style={[styles.saveButton, { backgroundColor: primaryColor, opacity: isLoading ? 0.7 : 1 }]}
|
|
380
|
+
onPress={handleSaveNotifications}
|
|
381
|
+
disabled={isLoading}
|
|
382
|
+
testID="save-notifications-button"
|
|
383
|
+
>
|
|
384
|
+
{isLoading ? (
|
|
385
|
+
<ActivityIndicator color="#FFFFFF" size="small" />
|
|
386
|
+
) : (
|
|
387
|
+
<Text style={styles.saveButtonText}>Save Preferences</Text>
|
|
388
|
+
)}
|
|
389
|
+
</TouchableOpacity>
|
|
390
|
+
</View>
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
return (
|
|
394
|
+
<View style={[styles.container, { backgroundColor }]}>
|
|
395
|
+
<ScrollView style={styles.scrollView} contentContainerStyle={styles.scrollContainer}>
|
|
396
|
+
<View style={styles.header}>
|
|
397
|
+
<TouchableOpacity
|
|
398
|
+
style={styles.backButton}
|
|
399
|
+
onPress={goBack}
|
|
400
|
+
>
|
|
401
|
+
<Text style={[styles.backButtonText, { color: primaryColor }]}>Back</Text>
|
|
402
|
+
</TouchableOpacity>
|
|
403
|
+
<Text style={[styles.title, { color: textColor }]}>Account Settings</Text>
|
|
404
|
+
<View style={styles.backButtonPlaceholder} />
|
|
405
|
+
</View>
|
|
406
|
+
|
|
407
|
+
<View style={[styles.tabsContainer, { borderColor }]}>
|
|
408
|
+
<TouchableOpacity
|
|
409
|
+
style={[
|
|
410
|
+
styles.tabButton,
|
|
411
|
+
currentTab === 'profile' && [styles.activeTabButton, { borderColor: primaryColor }]
|
|
412
|
+
]}
|
|
413
|
+
onPress={() => setCurrentTab('profile')}
|
|
414
|
+
>
|
|
415
|
+
<Text
|
|
416
|
+
style={[
|
|
417
|
+
styles.tabButtonText,
|
|
418
|
+
{ color: currentTab === 'profile' ? primaryColor : textColor }
|
|
419
|
+
]}
|
|
420
|
+
>
|
|
421
|
+
Profile
|
|
422
|
+
</Text>
|
|
423
|
+
</TouchableOpacity>
|
|
424
|
+
|
|
425
|
+
<TouchableOpacity
|
|
426
|
+
style={[
|
|
427
|
+
styles.tabButton,
|
|
428
|
+
currentTab === 'password' && [styles.activeTabButton, { borderColor: primaryColor }]
|
|
429
|
+
]}
|
|
430
|
+
onPress={() => setCurrentTab('password')}
|
|
431
|
+
>
|
|
432
|
+
<Text
|
|
433
|
+
style={[
|
|
434
|
+
styles.tabButtonText,
|
|
435
|
+
{ color: currentTab === 'password' ? primaryColor : textColor }
|
|
436
|
+
]}
|
|
437
|
+
>
|
|
438
|
+
Password
|
|
439
|
+
</Text>
|
|
440
|
+
</TouchableOpacity>
|
|
441
|
+
|
|
442
|
+
<TouchableOpacity
|
|
443
|
+
style={[
|
|
444
|
+
styles.tabButton,
|
|
445
|
+
currentTab === 'notifications' && [styles.activeTabButton, { borderColor: primaryColor }]
|
|
446
|
+
]}
|
|
447
|
+
onPress={() => setCurrentTab('notifications')}
|
|
448
|
+
>
|
|
449
|
+
<Text
|
|
450
|
+
style={[
|
|
451
|
+
styles.tabButtonText,
|
|
452
|
+
{ color: currentTab === 'notifications' ? primaryColor : textColor }
|
|
453
|
+
]}
|
|
454
|
+
>
|
|
455
|
+
Notifications
|
|
456
|
+
</Text>
|
|
457
|
+
</TouchableOpacity>
|
|
458
|
+
</View>
|
|
459
|
+
|
|
460
|
+
{errorMessage ? (
|
|
461
|
+
<View style={styles.errorContainer}>
|
|
462
|
+
<Text style={styles.errorText}>{errorMessage}</Text>
|
|
463
|
+
</View>
|
|
464
|
+
) : null}
|
|
465
|
+
|
|
466
|
+
{successMessage ? (
|
|
467
|
+
<View style={styles.successContainer}>
|
|
468
|
+
<Text style={styles.successText}>{successMessage}</Text>
|
|
469
|
+
</View>
|
|
470
|
+
) : null}
|
|
471
|
+
|
|
472
|
+
{currentTab === 'profile' && renderProfileTab()}
|
|
473
|
+
{currentTab === 'password' && renderPasswordTab()}
|
|
474
|
+
{currentTab === 'notifications' && renderNotificationsTab()}
|
|
475
|
+
</ScrollView>
|
|
476
|
+
</View>
|
|
477
|
+
);
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const styles = StyleSheet.create({
|
|
481
|
+
container: {
|
|
482
|
+
flex: 1,
|
|
483
|
+
},
|
|
484
|
+
scrollView: {
|
|
485
|
+
flex: 1,
|
|
486
|
+
},
|
|
487
|
+
scrollContainer: {
|
|
488
|
+
padding: 20,
|
|
489
|
+
},
|
|
490
|
+
header: {
|
|
491
|
+
flexDirection: 'row',
|
|
492
|
+
justifyContent: 'space-between',
|
|
493
|
+
alignItems: 'center',
|
|
494
|
+
marginBottom: 24,
|
|
495
|
+
},
|
|
496
|
+
title: {
|
|
497
|
+
fontSize: 24,
|
|
498
|
+
fontWeight: 'bold',
|
|
499
|
+
},
|
|
500
|
+
backButton: {
|
|
501
|
+
padding: 10,
|
|
502
|
+
},
|
|
503
|
+
backButtonText: {
|
|
504
|
+
fontSize: 16,
|
|
505
|
+
fontWeight: '600',
|
|
506
|
+
},
|
|
507
|
+
backButtonPlaceholder: {
|
|
508
|
+
width: 40,
|
|
509
|
+
},
|
|
510
|
+
tabsContainer: {
|
|
511
|
+
flexDirection: 'row',
|
|
512
|
+
marginBottom: 24,
|
|
513
|
+
borderBottomWidth: 1,
|
|
514
|
+
},
|
|
515
|
+
tabButton: {
|
|
516
|
+
flex: 1,
|
|
517
|
+
alignItems: 'center',
|
|
518
|
+
paddingVertical: 12,
|
|
519
|
+
},
|
|
520
|
+
activeTabButton: {
|
|
521
|
+
borderBottomWidth: 2,
|
|
522
|
+
},
|
|
523
|
+
tabButtonText: {
|
|
524
|
+
fontSize: 16,
|
|
525
|
+
fontWeight: '500',
|
|
526
|
+
},
|
|
527
|
+
tabContent: {
|
|
528
|
+
marginBottom: 24,
|
|
529
|
+
},
|
|
530
|
+
avatarSection: {
|
|
531
|
+
alignItems: 'center',
|
|
532
|
+
marginBottom: 24,
|
|
533
|
+
},
|
|
534
|
+
changeAvatarButton: {
|
|
535
|
+
marginTop: 12,
|
|
536
|
+
paddingVertical: 8,
|
|
537
|
+
paddingHorizontal: 16,
|
|
538
|
+
borderRadius: 20,
|
|
539
|
+
},
|
|
540
|
+
changeAvatarText: {
|
|
541
|
+
color: '#FFFFFF',
|
|
542
|
+
fontWeight: '600',
|
|
543
|
+
},
|
|
544
|
+
inputContainer: {
|
|
545
|
+
marginBottom: 16,
|
|
546
|
+
},
|
|
547
|
+
label: {
|
|
548
|
+
fontSize: 16,
|
|
549
|
+
marginBottom: 8,
|
|
550
|
+
},
|
|
551
|
+
input: {
|
|
552
|
+
height: 50,
|
|
553
|
+
borderRadius: 8,
|
|
554
|
+
borderWidth: 1,
|
|
555
|
+
paddingHorizontal: 12,
|
|
556
|
+
fontSize: 16,
|
|
557
|
+
},
|
|
558
|
+
textArea: {
|
|
559
|
+
minHeight: 100,
|
|
560
|
+
borderRadius: 8,
|
|
561
|
+
borderWidth: 1,
|
|
562
|
+
paddingHorizontal: 12,
|
|
563
|
+
paddingTop: 12,
|
|
564
|
+
fontSize: 16,
|
|
565
|
+
textAlignVertical: 'top',
|
|
566
|
+
},
|
|
567
|
+
passwordHint: {
|
|
568
|
+
fontSize: 14,
|
|
569
|
+
marginTop: 4,
|
|
570
|
+
},
|
|
571
|
+
saveButton: {
|
|
572
|
+
height: 50,
|
|
573
|
+
borderRadius: 25,
|
|
574
|
+
alignItems: 'center',
|
|
575
|
+
justifyContent: 'center',
|
|
576
|
+
marginTop: 16,
|
|
577
|
+
},
|
|
578
|
+
saveButtonText: {
|
|
579
|
+
color: '#FFFFFF',
|
|
580
|
+
fontSize: 16,
|
|
581
|
+
fontWeight: '600',
|
|
582
|
+
},
|
|
583
|
+
settingRow: {
|
|
584
|
+
flexDirection: 'row',
|
|
585
|
+
justifyContent: 'space-between',
|
|
586
|
+
alignItems: 'center',
|
|
587
|
+
paddingVertical: 16,
|
|
588
|
+
borderBottomWidth: 1,
|
|
589
|
+
borderBottomColor: '#E0E0E0',
|
|
590
|
+
},
|
|
591
|
+
settingLabel: {
|
|
592
|
+
fontSize: 16,
|
|
593
|
+
},
|
|
594
|
+
errorContainer: {
|
|
595
|
+
backgroundColor: '#FFEBEE',
|
|
596
|
+
padding: 16,
|
|
597
|
+
borderRadius: 8,
|
|
598
|
+
marginBottom: 16,
|
|
599
|
+
},
|
|
600
|
+
errorText: {
|
|
601
|
+
color: '#D32F2F',
|
|
602
|
+
fontSize: 14,
|
|
603
|
+
},
|
|
604
|
+
successContainer: {
|
|
605
|
+
backgroundColor: '#E8F5E9',
|
|
606
|
+
padding: 16,
|
|
607
|
+
borderRadius: 8,
|
|
608
|
+
marginBottom: 16,
|
|
609
|
+
},
|
|
610
|
+
successText: {
|
|
611
|
+
color: '#2E7D32',
|
|
612
|
+
fontSize: 14,
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
export default AccountSettingsScreen;
|
package/lib/commonjs/backend.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
var _exportNames = {
|
|
7
|
-
OxyServices: true,
|
|
8
|
-
OXY_CLOUD_URL: true
|
|
9
|
-
};
|
|
10
|
-
Object.defineProperty(exports, "OXY_CLOUD_URL", {
|
|
11
|
-
enumerable: true,
|
|
12
|
-
get: function () {
|
|
13
|
-
return _core.OXY_CLOUD_URL;
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
Object.defineProperty(exports, "OxyServices", {
|
|
17
|
-
enumerable: true,
|
|
18
|
-
get: function () {
|
|
19
|
-
return _core.OxyServices;
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
exports.default = void 0;
|
|
23
|
-
var _core = require("./core");
|
|
24
|
-
var _interfaces = require("./models/interfaces");
|
|
25
|
-
Object.keys(_interfaces).forEach(function (key) {
|
|
26
|
-
if (key === "default" || key === "__esModule") return;
|
|
27
|
-
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
28
|
-
if (key in exports && exports[key] === _interfaces[key]) return;
|
|
29
|
-
Object.defineProperty(exports, key, {
|
|
30
|
-
enumerable: true,
|
|
31
|
-
get: function () {
|
|
32
|
-
return _interfaces[key];
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
/**
|
|
37
|
-
* Backend-only exports for Node.js environments
|
|
38
|
-
* This file exports only the core functionality without React Native dependencies
|
|
39
|
-
*/
|
|
40
|
-
// Export core services
|
|
41
|
-
// Export models/interfaces
|
|
42
|
-
// Default export for backward compatibility
|
|
43
|
-
var _default = exports.default = _core.OxyServices;
|
|
44
|
-
//# sourceMappingURL=backend.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["_core","require","_interfaces","Object","keys","forEach","key","prototype","hasOwnProperty","call","_exportNames","exports","defineProperty","enumerable","get","_default","default","OxyServices"],"sourceRoot":"../../src","sources":["backend.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAMA,IAAAA,KAAA,GAAAC,OAAA;AAIA,IAAAC,WAAA,GAAAD,OAAA;AAAAE,MAAA,CAAAC,IAAA,CAAAF,WAAA,EAAAG,OAAA,WAAAC,GAAA;EAAA,IAAAA,GAAA,kBAAAA,GAAA;EAAA,IAAAH,MAAA,CAAAI,SAAA,CAAAC,cAAA,CAAAC,IAAA,CAAAC,YAAA,EAAAJ,GAAA;EAAA,IAAAA,GAAA,IAAAK,OAAA,IAAAA,OAAA,CAAAL,GAAA,MAAAJ,WAAA,CAAAI,GAAA;EAAAH,MAAA,CAAAS,cAAA,CAAAD,OAAA,EAAAL,GAAA;IAAAO,UAAA;IAAAC,GAAA,WAAAA,CAAA;MAAA,OAAAZ,WAAA,CAAAI,GAAA;IAAA;EAAA;AAAA;AAVA;AACA;AACA;AACA;AAEA;AAIA;AAGA;AAAA,IAAAS,QAAA,GAAAJ,OAAA,CAAAK,OAAA,GACeC,iBAAW","ignoreList":[]}
|