@oxyhq/services 5.6.0 → 5.6.2

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.
Files changed (29) hide show
  1. package/lib/commonjs/ui/context/OxyContext.js +124 -53
  2. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  3. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +84 -25
  4. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  5. package/lib/commonjs/ui/stores/authStore.js +36 -1
  6. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  7. package/lib/commonjs/utils/deviceManager.js +7 -3
  8. package/lib/commonjs/utils/deviceManager.js.map +1 -1
  9. package/lib/module/ui/context/OxyContext.js +125 -54
  10. package/lib/module/ui/context/OxyContext.js.map +1 -1
  11. package/lib/module/ui/screens/AccountSettingsScreen.js +84 -25
  12. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  13. package/lib/module/ui/stores/authStore.js +36 -1
  14. package/lib/module/ui/stores/authStore.js.map +1 -1
  15. package/lib/module/utils/deviceManager.js +7 -3
  16. package/lib/module/utils/deviceManager.js.map +1 -1
  17. package/lib/typescript/models/interfaces.d.ts +6 -0
  18. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  19. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  20. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  21. package/lib/typescript/ui/stores/authStore.d.ts +2 -0
  22. package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
  23. package/lib/typescript/utils/deviceManager.d.ts.map +1 -1
  24. package/package.json +2 -2
  25. package/src/models/interfaces.ts +7 -1
  26. package/src/ui/context/OxyContext.tsx +80 -56
  27. package/src/ui/screens/AccountSettingsScreen.tsx +68 -24
  28. package/src/ui/stores/authStore.ts +21 -0
  29. package/src/utils/deviceManager.ts +12 -4
@@ -1,10 +1,11 @@
1
- import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode, useMemo } from 'react';
1
+ import React, { createContext, useContext, useEffect, useCallback, ReactNode, useMemo } from 'react';
2
2
  import { OxyServices } from '../../core';
3
3
  import { User } from '../../models/interfaces';
4
4
  import { SecureLoginResponse, SecureClientSession, MinimalUserData } from '../../models/secureSession';
5
5
  import { DeviceManager } from '../../utils/deviceManager';
6
6
  import { useSessionSocket } from '../hooks/useSessionSocket';
7
7
  import { toast } from '../../lib/sonner';
8
+ import { useAuthStore } from '../stores/authStore';
8
9
 
9
10
  // Define the context shape
10
11
  export interface OxyContextState {
@@ -95,7 +96,7 @@ const getStorage = async (): Promise<StorageInterface> => {
95
96
  if (!AsyncStorage) {
96
97
  try {
97
98
  const asyncStorageModule = await import('@react-native-async-storage/async-storage');
98
- AsyncStorage = asyncStorageModule.default;
99
+ AsyncStorage = (asyncStorageModule.default as unknown) as StorageInterface;
99
100
  } catch (error) {
100
101
  console.error('Failed to import AsyncStorage:', error);
101
102
  throw new Error('AsyncStorage is required in React Native environment');
@@ -120,14 +121,22 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
120
121
  onAuthStateChange,
121
122
  bottomSheetRef,
122
123
  }) => {
123
- // Authentication state
124
- const [user, setUser] = useState<User | null>(null);
125
- const [minimalUser, setMinimalUser] = useState<MinimalUserData | null>(null);
126
- const [sessions, setSessions] = useState<SecureClientSession[]>([]);
127
- const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
128
- const [isLoading, setIsLoading] = useState(true);
129
- const [error, setError] = useState<string | null>(null);
130
- const [storage, setStorage] = useState<StorageInterface | null>(null);
124
+ // Zustand state
125
+ const user = useAuthStore((state) => state.user);
126
+ const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
127
+ const isLoading = useAuthStore((state) => state.isLoading);
128
+ const error = useAuthStore((state) => state.error);
129
+ const loginSuccess = useAuthStore((state) => state.loginSuccess);
130
+ const loginFailure = useAuthStore((state) => state.loginFailure);
131
+ const logoutStore = useAuthStore((state) => state.logout);
132
+
133
+ // Local state for non-auth fields
134
+ const [minimalUser, setMinimalUser] = React.useState<MinimalUserData | null>(null);
135
+ const [sessions, setSessions] = React.useState<SecureClientSession[]>([]);
136
+ const [activeSessionId, setActiveSessionId] = React.useState<string | null>(null);
137
+ const [storage, setStorage] = React.useState<StorageInterface | null>(null);
138
+ // Add a new state to track token restoration
139
+ const [tokenReady, setTokenReady] = React.useState(false);
131
140
 
132
141
  // Storage keys (memoized to prevent infinite loops)
133
142
  const keys = useMemo(() => getSecureStorageKeys(storageKeyPrefix), [storageKeyPrefix]);
@@ -140,7 +149,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
140
149
  setStorage(platformStorage);
141
150
  } catch (error) {
142
151
  console.error('Failed to initialize storage:', error);
143
- setError('Failed to initialize storage');
152
+ useAuthStore.setState({ error: 'Failed to initialize storage' });
144
153
  }
145
154
  };
146
155
 
@@ -152,7 +161,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
152
161
  const initAuth = async () => {
153
162
  if (!storage) return;
154
163
 
155
- setIsLoading(true);
164
+ useAuthStore.setState({ isLoading: true });
156
165
  try {
157
166
  // Load stored sessions
158
167
  const sessionsData = await storage.getItem(keys.sessions);
@@ -216,7 +225,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
216
225
 
217
226
  // Load full user data
218
227
  const fullUser = await oxyServices.getUserBySession(activeSession.sessionId);
219
- setUser(fullUser);
228
+ loginSuccess(fullUser);
220
229
  setMinimalUser({
221
230
  id: fullUser.id,
222
231
  username: fullUser.username,
@@ -241,7 +250,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
241
250
  console.error('Secure auth initialization error:', err);
242
251
  await clearAllStorage();
243
252
  } finally {
244
- setIsLoading(false);
253
+ useAuthStore.setState({ isLoading: false });
245
254
  }
246
255
  };
247
256
 
@@ -250,6 +259,26 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
250
259
  }
251
260
  }, [storage, oxyServices, keys, onAuthStateChange]);
252
261
 
262
+ // Effect to restore token on app load or session switch
263
+ useEffect(() => {
264
+ const restoreToken = async () => {
265
+ if (activeSessionId && oxyServices) {
266
+ try {
267
+ await oxyServices.getTokenBySession(activeSessionId);
268
+ setTokenReady(true);
269
+ } catch (err) {
270
+ // If token restoration fails, force logout
271
+ await logout();
272
+ setTokenReady(false);
273
+ }
274
+ } else {
275
+ setTokenReady(true); // No session, so token is not needed
276
+ }
277
+ };
278
+ restoreToken();
279
+ // Only run when activeSessionId or oxyServices changes
280
+ }, [activeSessionId, oxyServices]);
281
+
253
282
  // Remove invalid session
254
283
  const removeInvalidSession = useCallback(async (sessionId: string): Promise<void> => {
255
284
  const filteredSessions = sessions.filter(s => s.sessionId !== sessionId);
@@ -262,7 +291,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
262
291
  } else {
263
292
  // No valid sessions left
264
293
  setActiveSessionId(null);
265
- setUser(null);
294
+ logoutStore();
266
295
  setMinimalUser(null);
267
296
  await storage?.removeItem(keys.activeSessionId);
268
297
 
@@ -270,7 +299,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
270
299
  onAuthStateChange(null);
271
300
  }
272
301
  }
273
- }, [sessions, storage, keys, onAuthStateChange]);
302
+ }, [sessions, storage, keys, onAuthStateChange, logoutStore]);
274
303
 
275
304
  // Save sessions to storage
276
305
  const saveSessionsToStorage = useCallback(async (sessionsList: SecureClientSession[]): Promise<void> => {
@@ -298,7 +327,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
298
327
  // Switch to a different session
299
328
  const switchToSession = useCallback(async (sessionId: string): Promise<void> => {
300
329
  try {
301
- setIsLoading(true);
330
+ useAuthStore.setState({ isLoading: true });
302
331
 
303
332
  // Get access token for this session
304
333
  await oxyServices.getTokenBySession(sessionId);
@@ -307,7 +336,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
307
336
  const fullUser = await oxyServices.getUserBySession(sessionId);
308
337
 
309
338
  setActiveSessionId(sessionId);
310
- setUser(fullUser);
339
+ loginSuccess(fullUser);
311
340
  setMinimalUser({
312
341
  id: fullUser.id,
313
342
  username: fullUser.username,
@@ -321,18 +350,16 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
321
350
  }
322
351
  } catch (error) {
323
352
  console.error('Switch session error:', error);
324
- setError('Failed to switch session');
353
+ useAuthStore.setState({ error: 'Failed to switch session' });
325
354
  } finally {
326
- setIsLoading(false);
355
+ useAuthStore.setState({ isLoading: false });
327
356
  }
328
- }, [oxyServices, onAuthStateChange, saveActiveSessionId]);
357
+ }, [oxyServices, onAuthStateChange, loginSuccess, saveActiveSessionId]);
329
358
 
330
359
  // Secure login method
331
360
  const login = async (username: string, password: string, deviceName?: string): Promise<User> => {
332
361
  if (!storage) throw new Error('Storage not initialized');
333
-
334
- setIsLoading(true);
335
- setError(null);
362
+ useAuthStore.setState({ isLoading: true, error: null });
336
363
 
337
364
  try {
338
365
  // Get device fingerprint for enhanced device identification
@@ -398,7 +425,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
398
425
 
399
426
  // Load full user data
400
427
  const fullUser = await oxyServices.getUserBySession(response.sessionId);
401
- setUser(fullUser);
428
+ loginSuccess(fullUser);
402
429
  setMinimalUser(response.user);
403
430
 
404
431
  if (onAuthStateChange) {
@@ -407,10 +434,10 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
407
434
 
408
435
  return fullUser;
409
436
  } catch (error: any) {
410
- setError(error.message || 'Login failed');
437
+ loginFailure(error.message || 'Login failed');
411
438
  throw error;
412
439
  } finally {
413
- setIsLoading(false);
440
+ useAuthStore.setState({ isLoading: false });
414
441
  }
415
442
  };
416
443
 
@@ -435,7 +462,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
435
462
  } else {
436
463
  // No sessions left
437
464
  setActiveSessionId(null);
438
- setUser(null);
465
+ logoutStore();
439
466
  setMinimalUser(null);
440
467
  await storage?.removeItem(keys.activeSessionId);
441
468
 
@@ -446,7 +473,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
446
473
  }
447
474
  } catch (error) {
448
475
  console.error('Logout error:', error);
449
- setError('Logout failed');
476
+ useAuthStore.setState({ error: 'Logout failed' });
450
477
  }
451
478
  };
452
479
 
@@ -456,13 +483,13 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
456
483
 
457
484
  if (!activeSessionId) {
458
485
  console.error('No active session ID found, cannot logout all');
459
- setError('No active session found');
486
+ useAuthStore.setState({ error: 'No active session found' });
460
487
  throw new Error('No active session found');
461
488
  }
462
489
 
463
490
  if (!oxyServices) {
464
491
  console.error('OxyServices not initialized');
465
- setError('Service not available');
492
+ useAuthStore.setState({ error: 'Service not available' });
466
493
  throw new Error('Service not available');
467
494
  }
468
495
 
@@ -474,7 +501,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
474
501
  // Clear all local data
475
502
  setSessions([]);
476
503
  setActiveSessionId(null);
477
- setUser(null);
504
+ logoutStore();
478
505
  setMinimalUser(null);
479
506
  await clearAllStorage();
480
507
  console.log('Local storage cleared');
@@ -485,7 +512,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
485
512
  }
486
513
  } catch (error) {
487
514
  console.error('Logout all error:', error);
488
- setError(`Logout all failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
515
+ useAuthStore.setState({ error: `Logout all failed: ${error instanceof Error ? error.message : 'Unknown error'}` });
489
516
  throw error;
490
517
  }
491
518
  };
@@ -494,8 +521,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
494
521
  const signUp = async (username: string, email: string, password: string): Promise<User> => {
495
522
  if (!storage) throw new Error('Storage not initialized');
496
523
 
497
- setIsLoading(true);
498
- setError(null);
524
+ useAuthStore.setState({ isLoading: true, error: null });
499
525
 
500
526
  try {
501
527
  // Create new account using the OxyServices signUp method
@@ -509,10 +535,10 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
509
535
 
510
536
  return user;
511
537
  } catch (error: any) {
512
- setError(error.message || 'Sign up failed');
538
+ loginFailure(error.message || 'Sign up failed');
513
539
  throw error;
514
540
  } finally {
515
- setIsLoading(false);
541
+ useAuthStore.setState({ isLoading: false });
516
542
  }
517
543
  };
518
544
 
@@ -569,7 +595,7 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
569
595
  // Clear all local sessions since we logged out from all devices
570
596
  setSessions([]);
571
597
  setActiveSessionId(null);
572
- setUser(null);
598
+ logoutStore();
573
599
  setMinimalUser(null);
574
600
  await clearAllStorage();
575
601
 
@@ -640,16 +666,6 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
640
666
  }
641
667
  }, [bottomSheetRef]);
642
668
 
643
- // Compute comprehensive authentication status
644
- // This is the single source of truth for authentication across the entire app
645
- const isAuthenticated = useMemo(() => {
646
- // User is authenticated if:
647
- // 1. We have a full user object loaded, OR
648
- // 2. We have an active session with a valid token
649
- // This covers both the loaded state and the loading-but-authenticated state
650
- return !!user || (!!activeSessionId && !!oxyServices?.getCurrentUserId());
651
- }, [user, activeSessionId, oxyServices]);
652
-
653
669
  // Integrate socket for real-time session updates
654
670
  console.log('OxyContextProvider: userId', user?.id, 'baseURL', oxyServices.getBaseURL());
655
671
  useSessionSocket({
@@ -675,20 +691,28 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
675
691
  error,
676
692
  login,
677
693
  logout,
678
- logoutAll,
679
- signUp,
680
- switchSession,
681
- removeSession,
682
- refreshSessions,
683
- getDeviceSessions,
684
- logoutAllDeviceSessions,
685
- updateDeviceName,
694
+ logoutAll: async () => { await logout(); },
695
+ signUp: async (username, email, password) => {
696
+ await signUp(username, email, password);
697
+ return user as User; // Return the latest user from Zustand
698
+ },
699
+ switchSession: async (sessionId) => { await switchToSession(sessionId); },
700
+ removeSession: async (sessionId) => { await removeSession(sessionId); },
701
+ refreshSessions: async () => { await refreshSessions(); },
702
+ getDeviceSessions: async () => { return await getDeviceSessions(); },
703
+ logoutAllDeviceSessions: async () => { await logoutAllDeviceSessions(); },
704
+ updateDeviceName: async (deviceName) => { await updateDeviceName(deviceName); },
686
705
  oxyServices,
687
706
  bottomSheetRef,
688
707
  showBottomSheet,
689
708
  hideBottomSheet,
690
709
  };
691
710
 
711
+ // Wrap children rendering to block until token is ready
712
+ if (!tokenReady) {
713
+ return <div>Loading authentication...</div>;
714
+ }
715
+
692
716
  return (
693
717
  <OxyContext.Provider value={contextValue}>
694
718
  {children}
@@ -19,6 +19,7 @@ import { Ionicons } from '@expo/vector-icons';
19
19
  import { toast } from '../../lib/sonner';
20
20
  import { fontFamilies } from '../styles/fonts';
21
21
  import { confirmAction } from '../utils/confirmAction';
22
+ import { useAuthStore } from '../stores/authStore';
22
23
 
23
24
  const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
24
25
  onClose,
@@ -27,6 +28,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
27
28
  navigate,
28
29
  }) => {
29
30
  const { user, oxyServices, isLoading: authLoading, isAuthenticated } = useOxy();
31
+ const updateUser = useAuthStore((state) => state.updateUser);
30
32
  const [isLoading, setIsLoading] = useState(false);
31
33
  const [isSaving, setIsSaving] = useState(false);
32
34
 
@@ -35,11 +37,12 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
35
37
 
36
38
  // Form state
37
39
  const [displayName, setDisplayName] = useState('');
40
+ const [lastName, setLastName] = useState('');
38
41
  const [username, setUsername] = useState('');
39
42
  const [email, setEmail] = useState('');
40
43
  const [bio, setBio] = useState('');
41
44
  const [location, setLocation] = useState('');
42
- const [website, setWebsite] = useState('');
45
+ const [links, setLinks] = useState<string[]>([]);
43
46
  const [avatarUrl, setAvatarUrl] = useState('');
44
47
 
45
48
  // Editing states
@@ -47,11 +50,12 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
47
50
 
48
51
  // Temporary input states for inline editing
49
52
  const [tempDisplayName, setTempDisplayName] = useState('');
53
+ const [tempLastName, setTempLastName] = useState('');
50
54
  const [tempUsername, setTempUsername] = useState('');
51
55
  const [tempEmail, setTempEmail] = useState('');
52
56
  const [tempBio, setTempBio] = useState('');
53
57
  const [tempLocation, setTempLocation] = useState('');
54
- const [tempWebsite, setTempWebsite] = useState('');
58
+ const [tempLinks, setTempLinks] = useState<string[]>([]);
55
59
 
56
60
  // Memoize theme-related calculations to prevent unnecessary recalculations
57
61
  const themeStyles = useMemo(() => {
@@ -78,14 +82,19 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
78
82
  if (user) {
79
83
  const userDisplayName = typeof user.name === 'string'
80
84
  ? user.name
81
- : user.name?.full || user.name?.first || '';
82
-
85
+ : user.name?.first || user.name?.full || '';
86
+ const userLastName = typeof user.name === 'object' ? user.name?.last || '' : '';
83
87
  setDisplayName(userDisplayName);
88
+ setLastName(userLastName);
84
89
  setUsername(user.username || '');
85
90
  setEmail(user.email || '');
86
91
  setBio(user.bio || '');
87
92
  setLocation(user.location || '');
88
- setWebsite(user.website || '');
93
+ setLinks(
94
+ Array.isArray(user.links)
95
+ ? user.links.map(l => typeof l === 'string' ? l : l.link).filter(Boolean)
96
+ : user.website ? [user.website] : []
97
+ );
89
98
  setAvatarUrl(user.avatar?.url || '');
90
99
  }
91
100
  }, [user]);
@@ -102,12 +111,12 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
102
111
  email,
103
112
  bio,
104
113
  location,
105
- website,
114
+ links,
106
115
  };
107
116
 
108
117
  // Handle name field
109
- if (displayName) {
110
- updates.name = displayName;
118
+ if (displayName || lastName) {
119
+ updates.name = { first: displayName, last: lastName };
111
120
  }
112
121
 
113
122
  // Handle avatar
@@ -115,7 +124,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
115
124
  updates.avatar = { url: avatarUrl };
116
125
  }
117
126
 
118
- await oxyServices.updateProfile(updates);
127
+ await updateUser(updates, oxyServices);
119
128
  toast.success('Profile updated successfully');
120
129
 
121
130
  animateSaveButton(1); // Scale back to normal
@@ -144,7 +153,8 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
144
153
  const startEditing = (type: string, currentValue: string) => {
145
154
  switch (type) {
146
155
  case 'displayName':
147
- setTempDisplayName(currentValue);
156
+ setTempDisplayName(displayName);
157
+ setTempLastName(lastName);
148
158
  break;
149
159
  case 'username':
150
160
  setTempUsername(currentValue);
@@ -158,8 +168,8 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
158
168
  case 'location':
159
169
  setTempLocation(currentValue);
160
170
  break;
161
- case 'website':
162
- setTempWebsite(currentValue);
171
+ case 'links':
172
+ setTempLinks([...links]);
163
173
  break;
164
174
  }
165
175
  setEditingField(type);
@@ -171,6 +181,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
171
181
  switch (type) {
172
182
  case 'displayName':
173
183
  setDisplayName(tempDisplayName);
184
+ setLastName(tempLastName);
174
185
  break;
175
186
  case 'username':
176
187
  setUsername(tempUsername);
@@ -184,8 +195,8 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
184
195
  case 'location':
185
196
  setLocation(tempLocation);
186
197
  break;
187
- case 'website':
188
- setWebsite(tempWebsite);
198
+ case 'links':
199
+ setLinks(tempLinks);
189
200
  break;
190
201
  }
191
202
 
@@ -207,7 +218,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
207
218
  email: 'Email',
208
219
  bio: 'Bio',
209
220
  location: 'Location',
210
- website: 'Website'
221
+ links: 'Links'
211
222
  };
212
223
  return labels[type as keyof typeof labels] || 'Field';
213
224
  };
@@ -219,19 +230,52 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
219
230
  email: { name: 'mail', color: '#FF9500' },
220
231
  bio: { name: 'document-text', color: '#34C759' },
221
232
  location: { name: 'location', color: '#FF3B30' },
222
- website: { name: 'link', color: '#32D74B' }
233
+ links: { name: 'link', color: '#32D74B' }
223
234
  };
224
235
  return icons[type as keyof typeof icons] || { name: 'person', color: '#007AFF' };
225
236
  };
226
237
 
227
238
  const renderEditingField = (type: string) => {
239
+ if (type === 'displayName') {
240
+ return (
241
+ <View style={styles.editingFieldContainer}>
242
+ <View style={styles.editingFieldContent}>
243
+ <View style={[styles.newValueSection, { flexDirection: 'row', gap: 12 }]}>
244
+ <View style={{ flex: 1 }}>
245
+ <Text style={styles.editingFieldLabel}>First Name</Text>
246
+ <TextInput
247
+ style={styles.editingFieldInput}
248
+ value={tempDisplayName}
249
+ onChangeText={setTempDisplayName}
250
+ placeholder="Enter your first name"
251
+ placeholderTextColor={themeStyles.isDarkTheme ? '#aaa' : '#999'}
252
+ autoFocus
253
+ selectionColor={themeStyles.primaryColor}
254
+ />
255
+ </View>
256
+ <View style={{ flex: 1 }}>
257
+ <Text style={styles.editingFieldLabel}>Last Name</Text>
258
+ <TextInput
259
+ style={styles.editingFieldInput}
260
+ value={tempLastName}
261
+ onChangeText={setTempLastName}
262
+ placeholder="Enter your last name"
263
+ placeholderTextColor={themeStyles.isDarkTheme ? '#aaa' : '#999'}
264
+ selectionColor={themeStyles.primaryColor}
265
+ />
266
+ </View>
267
+ </View>
268
+ </View>
269
+ </View>
270
+ );
271
+ }
228
272
  const fieldConfig = {
229
273
  displayName: { label: 'Display Name', value: displayName, placeholder: 'Enter your display name', icon: 'person', color: '#007AFF', multiline: false, keyboardType: 'default' as const },
230
274
  username: { label: 'Username', value: username, placeholder: 'Choose a username', icon: 'at', color: '#5856D6', multiline: false, keyboardType: 'default' as const },
231
275
  email: { label: 'Email', value: email, placeholder: 'Enter your email address', icon: 'mail', color: '#FF9500', multiline: false, keyboardType: 'email-address' as const },
232
276
  bio: { label: 'Bio', value: bio, placeholder: 'Tell people about yourself...', icon: 'document-text', color: '#34C759', multiline: true, keyboardType: 'default' as const },
233
277
  location: { label: 'Location', value: location, placeholder: 'Enter your location', icon: 'location', color: '#FF3B30', multiline: false, keyboardType: 'default' as const },
234
- website: { label: 'Website', value: website, placeholder: 'Enter your website URL', icon: 'link', color: '#32D74B', multiline: false, keyboardType: 'url' as const }
278
+ links: { label: 'Links', value: links.join(', '), placeholder: 'Enter your links (comma separated)', icon: 'link', color: '#32D74B', multiline: false, keyboardType: 'url' as const }
235
279
  };
236
280
 
237
281
  const config = fieldConfig[type as keyof typeof fieldConfig];
@@ -244,7 +288,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
244
288
  case 'email': return tempEmail;
245
289
  case 'bio': return tempBio;
246
290
  case 'location': return tempLocation;
247
- case 'website': return tempWebsite;
291
+ case 'links': return tempLinks.join(', ');
248
292
  default: return '';
249
293
  }
250
294
  })();
@@ -256,7 +300,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
256
300
  case 'email': setTempEmail(text); break;
257
301
  case 'bio': setTempBio(text); break;
258
302
  case 'location': setTempLocation(text); break;
259
- case 'website': setTempWebsite(text); break;
303
+ case 'links': setTempLinks(text.split(',').map(s => s.trim()).filter(Boolean)); break;
260
304
  }
261
305
  };
262
306
 
@@ -446,7 +490,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
446
490
  {renderField(
447
491
  'displayName',
448
492
  'Display Name',
449
- displayName,
493
+ [displayName, lastName].filter(Boolean).join(' '), // Show full name
450
494
  'Add your display name',
451
495
  'person',
452
496
  '#007AFF',
@@ -514,10 +558,10 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
514
558
  )}
515
559
 
516
560
  {renderField(
517
- 'website',
518
- 'Website',
519
- website,
520
- 'Add your website',
561
+ 'links',
562
+ 'Links',
563
+ links.join(', '),
564
+ 'Add your links',
521
565
  'link',
522
566
  '#32D74B',
523
567
  false,
@@ -10,6 +10,8 @@ interface AuthState {
10
10
  loginSuccess: (user: User) => void;
11
11
  loginFailure: (error: string) => void;
12
12
  logout: () => void;
13
+ fetchUser: (oxyServices: any) => Promise<void>;
14
+ updateUser: (updates: Partial<User>, oxyServices: any) => Promise<void>;
13
15
  }
14
16
 
15
17
  export const useAuthStore = create<AuthState>((set: (state: Partial<AuthState>) => void) => ({
@@ -21,4 +23,23 @@ export const useAuthStore = create<AuthState>((set: (state: Partial<AuthState>)
21
23
  loginSuccess: (user: User) => set({ isLoading: false, isAuthenticated: true, user }),
22
24
  loginFailure: (error: string) => set({ isLoading: false, error }),
23
25
  logout: () => set({ user: null, isAuthenticated: false }),
26
+ fetchUser: async (oxyServices) => {
27
+ set({ isLoading: true, error: null });
28
+ try {
29
+ const user = await oxyServices.getCurrentUser();
30
+ set({ user, isLoading: false, isAuthenticated: true });
31
+ } catch (error: any) {
32
+ set({ error: error.message || 'Failed to fetch user', isLoading: false });
33
+ }
34
+ },
35
+ updateUser: async (updates, oxyServices) => {
36
+ set({ isLoading: true, error: null });
37
+ try {
38
+ await oxyServices.updateProfile(updates);
39
+ // Immediately fetch the latest user data after update
40
+ await useAuthStore.getState().fetchUser(oxyServices);
41
+ } catch (error: any) {
42
+ set({ error: error.message || 'Failed to update user', isLoading: false });
43
+ }
44
+ },
24
45
  }));
@@ -35,12 +35,20 @@ export class DeviceManager {
35
35
  /**
36
36
  * Get appropriate storage for the platform
37
37
  */
38
- private static async getStorage() {
38
+ private static async getStorage(): Promise<{
39
+ getItem: (key: string) => Promise<string | null>;
40
+ setItem: (key: string, value: string) => Promise<void>;
41
+ removeItem: (key: string) => Promise<void>;
42
+ }> {
39
43
  if (this.isReactNative()) {
40
- // Try to import AsyncStorage for React Native
41
44
  try {
42
- const AsyncStorage = await import('@react-native-async-storage/async-storage');
43
- return AsyncStorage.default;
45
+ const asyncStorageModule = await import('@react-native-async-storage/async-storage');
46
+ const storage = (asyncStorageModule.default as unknown) as import('@react-native-async-storage/async-storage').AsyncStorageStatic;
47
+ return {
48
+ getItem: storage.getItem.bind(storage),
49
+ setItem: storage.setItem.bind(storage),
50
+ removeItem: storage.removeItem.bind(storage),
51
+ };
44
52
  } catch (error) {
45
53
  console.error('AsyncStorage not available in React Native:', error);
46
54
  throw new Error('AsyncStorage is required in React Native environment');