@oxyhq/services 5.3.5 → 5.3.7

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 (33) hide show
  1. package/lib/commonjs/core/index.js +12 -1
  2. package/lib/commonjs/core/index.js.map +1 -1
  3. package/lib/commonjs/ui/context/OxyContext.js +17 -2
  4. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  5. package/lib/commonjs/ui/navigation/OxyRouter.js +0 -5
  6. package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
  7. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +479 -313
  8. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  9. package/lib/module/core/index.js +12 -1
  10. package/lib/module/core/index.js.map +1 -1
  11. package/lib/module/ui/context/OxyContext.js +17 -2
  12. package/lib/module/ui/context/OxyContext.js.map +1 -1
  13. package/lib/module/ui/navigation/OxyRouter.js +0 -5
  14. package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
  15. package/lib/module/ui/screens/AccountSwitcherScreen.js +480 -314
  16. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  17. package/lib/typescript/core/index.d.ts.map +1 -1
  18. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  19. package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
  20. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts +2 -2
  21. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  22. package/package.json +1 -1
  23. package/src/core/index.ts +13 -1
  24. package/src/ui/context/OxyContext.tsx +19 -2
  25. package/src/ui/navigation/OxyRouter.tsx +0 -5
  26. package/src/ui/screens/AccountSwitcherScreen.tsx +502 -304
  27. package/lib/commonjs/ui/screens/ModernAccountSwitcherScreen.js +0 -532
  28. package/lib/commonjs/ui/screens/ModernAccountSwitcherScreen.js.map +0 -1
  29. package/lib/module/ui/screens/ModernAccountSwitcherScreen.js +0 -527
  30. package/lib/module/ui/screens/ModernAccountSwitcherScreen.js.map +0 -1
  31. package/lib/typescript/ui/screens/ModernAccountSwitcherScreen.d.ts +0 -5
  32. package/lib/typescript/ui/screens/ModernAccountSwitcherScreen.d.ts.map +0 -1
  33. package/src/ui/screens/ModernAccountSwitcherScreen.tsx +0 -552
@@ -8,6 +8,7 @@ import {
8
8
  ScrollView,
9
9
  Alert,
10
10
  Platform,
11
+ Image,
11
12
  Dimensions,
12
13
  } from 'react-native';
13
14
  import { BaseScreenProps } from '../navigation/types';
@@ -16,6 +17,11 @@ import { SecureClientSession } from '../../models/secureSession';
16
17
  import { fontFamilies } from '../styles/fonts';
17
18
  import { User } from '../../models/interfaces';
18
19
 
20
+ interface SessionWithUser extends SecureClientSession {
21
+ userProfile?: User;
22
+ isLoadingProfile?: boolean;
23
+ }
24
+
19
25
  interface DeviceSession {
20
26
  sessionId: string;
21
27
  deviceId: string;
@@ -26,7 +32,7 @@ interface DeviceSession {
26
32
  isCurrent: boolean;
27
33
  }
28
34
 
29
- const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
35
+ const ModernAccountSwitcherScreen: React.FC<BaseScreenProps> = ({
30
36
  onClose,
31
37
  theme,
32
38
  navigate,
@@ -43,11 +49,16 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
43
49
  isLoading
44
50
  } = useOxy();
45
51
 
46
- const [allDeviceSessions, setAllDeviceSessions] = useState<DeviceSession[]>([]);
47
- const [loadingDeviceSessions, setLoadingDeviceSessions] = useState(false);
52
+ const [sessionsWithUsers, setSessionsWithUsers] = useState<SessionWithUser[]>([]);
48
53
  const [switchingToUserId, setSwitchingToUserId] = useState<string | null>(null);
49
54
  const [removingUserId, setRemovingUserId] = useState<string | null>(null);
50
- const [showRemoteDevices, setShowRemoteDevices] = useState(false);
55
+
56
+ // Device session management state
57
+ const [showDeviceManagement, setShowDeviceManagement] = useState(false);
58
+ const [deviceSessions, setDeviceSessions] = useState<DeviceSession[]>([]);
59
+ const [loadingDeviceSessions, setLoadingDeviceSessions] = useState(false);
60
+ const [remotingLogoutSessionId, setRemoteLogoutSessionId] = useState<string | null>(null);
61
+ const [loggingOutAllDevices, setLoggingOutAllDevices] = useState(false);
51
62
 
52
63
  const screenWidth = Dimensions.get('window').width;
53
64
  const isDarkTheme = theme === 'dark';
@@ -67,37 +78,46 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
67
78
  shadow: isDarkTheme ? 'rgba(0,0,0,0.3)' : 'rgba(0,0,0,0.1)',
68
79
  };
69
80
 
70
- // Load all device sessions for remote management
71
- const loadAllDeviceSessions = async () => {
72
- if (!activeSessionId || !oxyServices) return;
73
-
74
- setLoadingDeviceSessions(true);
75
- try {
76
- const allSessions = await oxyServices.getSessionsBySessionId(activeSessionId);
77
- const deviceSessions: DeviceSession[] = allSessions.map((session: any) => ({
78
- sessionId: session.sessionId,
79
- deviceId: session.deviceId,
80
- deviceName: session.deviceName || 'Unknown Device',
81
- isActive: session.isActive,
82
- lastActive: session.lastActive || new Date().toISOString(),
83
- expiresAt: session.expiresAt || new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(),
84
- isCurrent: session.sessionId === activeSessionId
81
+ // Load user profiles for sessions
82
+ useEffect(() => {
83
+ const loadUserProfiles = async () => {
84
+ if (!sessions.length || !oxyServices) return;
85
+
86
+ const updatedSessions: SessionWithUser[] = sessions.map(session => ({
87
+ ...session,
88
+ isLoadingProfile: true,
85
89
  }));
86
- setAllDeviceSessions(deviceSessions);
87
- } catch (error) {
88
- console.error('Failed to load device sessions:', error);
89
- Alert.alert('Error', 'Failed to load device sessions');
90
- } finally {
91
- setLoadingDeviceSessions(false);
92
- }
93
- };
90
+ setSessionsWithUsers(updatedSessions);
94
91
 
95
- // Load device sessions when showing remote devices
96
- useEffect(() => {
97
- if (showRemoteDevices) {
98
- loadAllDeviceSessions();
99
- }
100
- }, [showRemoteDevices, activeSessionId, oxyServices]);
92
+ // Load profiles for each session
93
+ for (let i = 0; i < sessions.length; i++) {
94
+ const session = sessions[i];
95
+ try {
96
+ // Try to get user profile using the session
97
+ const userProfile = await oxyServices.getUserBySession(session.sessionId);
98
+
99
+ setSessionsWithUsers(prev =>
100
+ prev.map(s =>
101
+ s.sessionId === session.sessionId
102
+ ? { ...s, userProfile, isLoadingProfile: false }
103
+ : s
104
+ )
105
+ );
106
+ } catch (error) {
107
+ console.error(`Failed to load profile for session ${session.sessionId}:`, error);
108
+ setSessionsWithUsers(prev =>
109
+ prev.map(s =>
110
+ s.sessionId === session.sessionId
111
+ ? { ...s, isLoadingProfile: false }
112
+ : s
113
+ )
114
+ );
115
+ }
116
+ }
117
+ };
118
+
119
+ loadUserProfiles();
120
+ }, [sessions, oxyServices]);
101
121
 
102
122
  const handleSwitchSession = async (sessionId: string) => {
103
123
  if (sessionId === user?.sessionId) return; // Already active session
@@ -118,12 +138,16 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
118
138
  };
119
139
 
120
140
  const handleRemoveSession = async (sessionId: string) => {
121
- const sessionToRemove = sessions.find(s => s.sessionId === sessionId);
141
+ const sessionToRemove = sessionsWithUsers.find(s => s.sessionId === sessionId);
122
142
  if (!sessionToRemove) return;
123
143
 
144
+ const displayName = typeof sessionToRemove.userProfile?.name === 'object'
145
+ ? sessionToRemove.userProfile.name.full || sessionToRemove.userProfile.name.first || sessionToRemove.userProfile.username
146
+ : sessionToRemove.userProfile?.name || sessionToRemove.userProfile?.username || 'this account';
147
+
124
148
  Alert.alert(
125
149
  'Remove Account',
126
- `Are you sure you want to remove this session from this device? You'll need to sign in again to access this account.`,
150
+ `Are you sure you want to remove ${displayName} from this device? You'll need to sign in again to access this account.`,
127
151
  [
128
152
  {
129
153
  text: 'Cancel',
@@ -151,6 +175,10 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
151
175
  };
152
176
 
153
177
  const handleLogoutAll = async () => {
178
+ console.log('handleLogoutAll called, current user:', user);
179
+ console.log('activeSessionId:', activeSessionId);
180
+ console.log('sessions count:', sessions.length);
181
+
154
182
  Alert.alert(
155
183
  'Sign Out All',
156
184
  'Are you sure you want to sign out of all accounts on this device?',
@@ -163,15 +191,19 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
163
191
  text: 'Sign Out All',
164
192
  style: 'destructive',
165
193
  onPress: async () => {
194
+ console.log('User confirmed logout all');
166
195
  try {
196
+ console.log('About to call logoutAll()');
167
197
  await logoutAll();
198
+ console.log('logoutAll() completed successfully');
168
199
  Alert.alert('Success', 'All accounts signed out successfully!');
169
200
  if (onClose) {
170
201
  onClose();
171
202
  }
172
203
  } catch (error) {
173
204
  console.error('Logout all failed:', error);
174
- Alert.alert('Logout Failed', 'There was a problem signing out. Please try again.');
205
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
206
+ Alert.alert('Logout Failed', `There was a problem signing out: ${errorMessage}`);
175
207
  }
176
208
  },
177
209
  },
@@ -180,27 +212,47 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
180
212
  );
181
213
  };
182
214
 
215
+ // Device session management functions
216
+ const loadAllDeviceSessions = async () => {
217
+ if (!oxyServices || !user?.sessionId) return;
218
+
219
+ setLoadingDeviceSessions(true);
220
+ try {
221
+ // This would call the API to get all device sessions for the current user
222
+ const allSessions = await oxyServices.getDeviceSessions(user.sessionId);
223
+ setDeviceSessions(allSessions || []);
224
+ } catch (error) {
225
+ console.error('Failed to load device sessions:', error);
226
+ Alert.alert('Error', 'Failed to load device sessions. Please try again.');
227
+ } finally {
228
+ setLoadingDeviceSessions(false);
229
+ }
230
+ };
231
+
183
232
  const handleRemoteSessionLogout = async (sessionId: string, deviceName: string) => {
184
233
  Alert.alert(
185
- 'Logout Remote Session',
186
- `Are you sure you want to logout from "${deviceName}"?`,
234
+ 'Remove Device Session',
235
+ `Are you sure you want to sign out from "${deviceName}"? This will end the session on that device.`,
187
236
  [
188
237
  {
189
238
  text: 'Cancel',
190
239
  style: 'cancel',
191
240
  },
192
241
  {
193
- text: 'Logout',
242
+ text: 'Sign Out',
194
243
  style: 'destructive',
195
244
  onPress: async () => {
245
+ setRemoteLogoutSessionId(sessionId);
196
246
  try {
197
- await oxyServices.logoutSecureSession(activeSessionId!, sessionId);
198
- Alert.alert('Success', 'Remote session logged out successfully!');
199
- // Refresh the device sessions list
247
+ await oxyServices?.logoutSecureSession(user?.sessionId || '', sessionId);
248
+ // Refresh device sessions list
200
249
  await loadAllDeviceSessions();
250
+ Alert.alert('Success', `Signed out from ${deviceName} successfully!`);
201
251
  } catch (error) {
202
252
  console.error('Remote logout failed:', error);
203
- Alert.alert('Logout Failed', 'There was a problem logging out the remote session. Please try again.');
253
+ Alert.alert('Logout Failed', 'There was a problem signing out from the device. Please try again.');
254
+ } finally {
255
+ setRemoteLogoutSessionId(null);
204
256
  }
205
257
  },
206
258
  },
@@ -210,26 +262,36 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
210
262
  };
211
263
 
212
264
  const handleLogoutAllDevices = async () => {
265
+ const otherDevicesCount = deviceSessions.filter(session => !session.isCurrent).length;
266
+
267
+ if (otherDevicesCount === 0) {
268
+ Alert.alert('No Other Devices', 'No other device sessions found to sign out from.');
269
+ return;
270
+ }
271
+
213
272
  Alert.alert(
214
- 'Logout All Devices',
215
- 'Are you sure you want to logout from all other devices? This will keep your current session active.',
273
+ 'Sign Out All Other Devices',
274
+ `Are you sure you want to sign out from all ${otherDevicesCount} other device(s)? This will end sessions on all other devices except this one.`,
216
275
  [
217
276
  {
218
277
  text: 'Cancel',
219
278
  style: 'cancel',
220
279
  },
221
280
  {
222
- text: 'Logout All Others',
281
+ text: 'Sign Out All',
223
282
  style: 'destructive',
224
283
  onPress: async () => {
284
+ setLoggingOutAllDevices(true);
225
285
  try {
226
- await oxyServices.logoutAllDeviceSessions(activeSessionId!, undefined, true);
227
- Alert.alert('Success', 'All other device sessions logged out successfully!');
228
- // Refresh the device sessions list
286
+ await oxyServices?.logoutAllDeviceSessions(user?.sessionId || '', undefined, true);
287
+ // Refresh device sessions list
229
288
  await loadAllDeviceSessions();
289
+ Alert.alert('Success', `Signed out from all other devices successfully!`);
230
290
  } catch (error) {
231
291
  console.error('Logout all devices failed:', error);
232
- Alert.alert('Logout Failed', 'There was a problem logging out other devices. Please try again.');
292
+ Alert.alert('Logout Failed', 'There was a problem signing out from other devices. Please try again.');
293
+ } finally {
294
+ setLoggingOutAllDevices(false);
233
295
  }
234
296
  },
235
297
  },
@@ -238,108 +300,168 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
238
300
  );
239
301
  };
240
302
 
241
- const renderSessionItem = (session: SecureClientSession) => {
242
- const isActive = session.sessionId === activeSessionId;
243
- const isSwitching = switchingToUserId === session.sessionId;
244
- const isRemoving = removingUserId === session.sessionId;
245
-
303
+ const renderDeviceSessionItem = (deviceSession: DeviceSession) => {
304
+ const isLoggingOut = remotingLogoutSessionId === deviceSession.sessionId;
305
+
246
306
  return (
247
307
  <View
248
- key={session.sessionId}
308
+ key={deviceSession.sessionId}
249
309
  style={[
250
- styles.userItem,
310
+ styles.sessionCard,
251
311
  {
252
- backgroundColor: isActive ? colors.activeCard : colors.surface,
253
- borderColor: isActive ? colors.accent : colors.border,
312
+ backgroundColor: deviceSession.isCurrent ? colors.activeCard : colors.card,
313
+ borderColor: deviceSession.isCurrent ? colors.accent : colors.border,
314
+ borderWidth: deviceSession.isCurrent ? 2 : 1,
254
315
  },
255
316
  ]}
256
317
  >
257
- <View style={styles.userInfo}>
258
- <Text style={[styles.username, { color: colors.text }]}>
259
- {isActive ? user?.username || 'Current Account' : 'Account Session'}
260
- </Text>
261
- <Text style={[styles.email, { color: isDarkTheme ? '#BBBBBB' : '#666666' }]}>
262
- Last active: {new Date(session.lastActive).toLocaleDateString()}
263
- </Text>
264
- {isActive && (
265
- <View style={[styles.activeBadge, { backgroundColor: colors.success }]}>
266
- <Text style={styles.activeBadgeText}>Active</Text>
267
- </View>
268
- )}
269
- </View>
270
-
271
- <View style={styles.userActions}>
272
- {!isActive && (
318
+ <View style={styles.sessionHeader}>
319
+ <View style={styles.userInfo}>
320
+ <Text style={[styles.displayName, { color: colors.text }]} numberOfLines={1}>
321
+ {deviceSession.deviceName}
322
+ {deviceSession.isCurrent && (
323
+ <Text style={[styles.username, { color: colors.accent }]}>
324
+ {' (This Device)'}
325
+ </Text>
326
+ )}
327
+ </Text>
328
+ <Text style={[styles.username, { color: colors.secondaryText }]} numberOfLines={1}>
329
+ ID: ...{deviceSession.deviceId.slice(-8)}
330
+ </Text>
331
+ <Text style={[styles.lastActive, { color: colors.secondaryText }]} numberOfLines={1}>
332
+ Last active: {new Date(deviceSession.lastActive).toLocaleDateString()}
333
+ </Text>
334
+ </View>
335
+
336
+ {!deviceSession.isCurrent && (
273
337
  <TouchableOpacity
274
- style={[styles.switchButton, { borderColor: colors.accent }]}
275
- onPress={() => handleSwitchSession(session.sessionId)}
276
- disabled={isSwitching || isRemoving}
338
+ style={[styles.removeButton, {
339
+ borderColor: colors.destructive,
340
+ backgroundColor: colors.background,
341
+ }]}
342
+ onPress={() => handleRemoteSessionLogout(deviceSession.sessionId, deviceSession.deviceName)}
343
+ disabled={isLoggingOut}
277
344
  >
278
- {isSwitching ? (
279
- <ActivityIndicator color={colors.accent} size="small" />
345
+ {isLoggingOut ? (
346
+ <ActivityIndicator color={colors.destructive} size="small" />
280
347
  ) : (
281
- <Text style={[styles.switchButtonText, { color: colors.accent }]}>Switch</Text>
348
+ <Text style={[styles.removeButtonText, { color: colors.destructive }]}>
349
+ Sign Out
350
+ </Text>
282
351
  )}
283
352
  </TouchableOpacity>
284
353
  )}
285
-
286
- <TouchableOpacity
287
- style={[styles.removeButton, { borderColor: colors.destructive }]}
288
- onPress={() => handleRemoveSession(session.sessionId)}
289
- disabled={isSwitching || isRemoving || sessions.length === 1}
290
- >
291
- {isRemoving ? (
292
- <ActivityIndicator color={colors.destructive} size="small" />
293
- ) : (
294
- <Text style={[styles.removeButtonText, { color: colors.destructive }]}>Remove</Text>
295
- )}
296
- </TouchableOpacity>
297
354
  </View>
298
355
  </View>
299
356
  );
300
357
  };
301
358
 
302
- const renderDeviceSessionItem = (deviceSession: DeviceSession) => {
303
- const isCurrentSession = deviceSession.isCurrent;
304
-
359
+ // Load device sessions when device management is shown
360
+ useEffect(() => {
361
+ if (showDeviceManagement && deviceSessions.length === 0) {
362
+ loadAllDeviceSessions();
363
+ }
364
+ }, [showDeviceManagement]);
365
+
366
+ const renderSessionItem = (sessionWithUser: SessionWithUser) => {
367
+ const isActive = sessionWithUser.sessionId === activeSessionId;
368
+ const isSwitching = switchingToUserId === sessionWithUser.sessionId;
369
+ const isRemoving = removingUserId === sessionWithUser.sessionId;
370
+ const { userProfile, isLoadingProfile } = sessionWithUser;
371
+
372
+ const displayName = typeof userProfile?.name === 'object'
373
+ ? userProfile.name.full || userProfile.name.first || userProfile.username
374
+ : userProfile?.name || userProfile?.username || 'Unknown User';
375
+ const username = userProfile?.username || 'unknown';
376
+ const avatarUrl = userProfile?.avatar?.url;
377
+
305
378
  return (
306
379
  <View
307
- key={deviceSession.sessionId}
380
+ key={sessionWithUser.sessionId}
308
381
  style={[
309
- styles.userItem,
382
+ styles.sessionCard,
310
383
  {
311
- backgroundColor: isCurrentSession ? colors.activeCard : colors.surface,
312
- borderColor: isCurrentSession ? colors.accent : colors.border,
384
+ backgroundColor: isActive ? colors.activeCard : colors.card,
385
+ borderColor: isActive ? colors.accent : colors.border,
386
+ borderWidth: isActive ? 2 : 1,
387
+ shadowColor: colors.shadow,
313
388
  },
314
389
  ]}
315
390
  >
316
- <View style={styles.userInfo}>
317
- <Text style={[styles.username, { color: colors.text }]}>
318
- {deviceSession.deviceName}
319
- {isCurrentSession && ' (This Device)'}
320
- </Text>
321
- <Text style={[styles.email, { color: isDarkTheme ? '#BBBBBB' : '#666666' }]}>
322
- Last active: {new Date(deviceSession.lastActive).toLocaleDateString()}
323
- </Text>
324
- <Text style={[styles.email, { color: isDarkTheme ? '#BBBBBB' : '#666666' }]}>
325
- Device ID: {deviceSession.deviceId.substring(0, 8)}...
326
- </Text>
327
- {isCurrentSession && (
328
- <View style={[styles.activeBadge, { backgroundColor: colors.success }]}>
329
- <Text style={styles.activeBadgeText}>Current</Text>
330
- </View>
331
- )}
391
+ <View style={styles.sessionHeader}>
392
+ <View style={styles.avatarContainer}>
393
+ {isLoadingProfile ? (
394
+ <View style={[styles.avatarPlaceholder, { backgroundColor: colors.surface }]}>
395
+ <ActivityIndicator size="small" color={colors.accent} />
396
+ </View>
397
+ ) : avatarUrl ? (
398
+ <Image
399
+ source={{ uri: avatarUrl }}
400
+ style={styles.avatar}
401
+ />
402
+ ) : (
403
+ <View style={[styles.avatarPlaceholder, { backgroundColor: colors.surface }]}>
404
+ <Text style={[styles.avatarText, { color: colors.accent }]}>
405
+ {displayName.charAt(0).toUpperCase()}
406
+ </Text>
407
+ </View>
408
+ )}
409
+ {isActive && (
410
+ <View style={[styles.activeBadge, { backgroundColor: colors.success }]}>
411
+ <Text style={styles.activeBadgeText}>✓</Text>
412
+ </View>
413
+ )}
414
+ </View>
415
+
416
+ <View style={styles.userInfo}>
417
+ <Text style={[styles.displayName, { color: colors.text }]} numberOfLines={1}>
418
+ {displayName}
419
+ </Text>
420
+ <Text style={[styles.username, { color: colors.secondaryText }]} numberOfLines={1}>
421
+ @{username}
422
+ </Text>
423
+ <Text style={[styles.lastActive, { color: colors.secondaryText }]} numberOfLines={1}>
424
+ Last active: {new Date(sessionWithUser.lastActive).toLocaleDateString()}
425
+ </Text>
426
+ </View>
332
427
  </View>
333
428
 
334
- <View style={styles.userActions}>
335
- {!isCurrentSession && (
429
+ <View style={styles.sessionActions}>
430
+ {!isActive && (
336
431
  <TouchableOpacity
337
- style={[styles.removeButton, { borderColor: colors.destructive }]}
338
- onPress={() => handleRemoteSessionLogout(deviceSession.sessionId, deviceSession.deviceName)}
432
+ style={[styles.switchButton, {
433
+ borderColor: colors.accent,
434
+ backgroundColor: colors.background,
435
+ }]}
436
+ onPress={() => handleSwitchSession(sessionWithUser.sessionId)}
437
+ disabled={isSwitching || isRemoving}
339
438
  >
340
- <Text style={[styles.removeButtonText, { color: colors.destructive }]}>Logout</Text>
439
+ {isSwitching ? (
440
+ <ActivityIndicator color={colors.accent} size="small" />
441
+ ) : (
442
+ <Text style={[styles.switchButtonText, { color: colors.accent }]}>
443
+ Switch
444
+ </Text>
445
+ )}
341
446
  </TouchableOpacity>
342
447
  )}
448
+
449
+ <TouchableOpacity
450
+ style={[styles.removeButton, {
451
+ borderColor: colors.destructive,
452
+ backgroundColor: colors.background,
453
+ }]}
454
+ onPress={() => handleRemoveSession(sessionWithUser.sessionId)}
455
+ disabled={isSwitching || isRemoving}
456
+ >
457
+ {isRemoving ? (
458
+ <ActivityIndicator color={colors.destructive} size="small" />
459
+ ) : (
460
+ <Text style={[styles.removeButtonText, { color: colors.destructive }]}>
461
+ Remove
462
+ </Text>
463
+ )}
464
+ </TouchableOpacity>
343
465
  </View>
344
466
  </View>
345
467
  );
@@ -351,103 +473,160 @@ const AccountSwitcherScreen: React.FC<BaseScreenProps> = ({
351
473
  <TouchableOpacity style={styles.backButton} onPress={goBack}>
352
474
  <Text style={[styles.backButtonText, { color: colors.accent }]}>‹ Back</Text>
353
475
  </TouchableOpacity>
354
- <Text style={[styles.title, { color: colors.text }]}>Account Switcher</Text>
355
- <View style={styles.placeholder} />
476
+ <Text style={[styles.title, { color: colors.text }]}>Accounts</Text>
477
+ <View style={styles.headerSpacer} />
356
478
  </View>
357
479
 
358
- <ScrollView style={styles.scrollView} contentContainerStyle={styles.scrollContainer}>
359
- <View style={styles.description}>
360
- <Text style={[styles.descriptionText, { color: isDarkTheme ? '#BBBBBB' : '#666666' }]}>
361
- Manage multiple accounts on this device. Switch between accounts or remove them from this device.
362
- </Text>
363
- </View>
364
-
365
- <View style={styles.usersContainer}>
366
- <Text style={[styles.sectionTitle, { color: colors.text }]}>
367
- Local Sessions ({sessions.length})
368
- </Text>
369
-
370
- {sessions.map(renderSessionItem)}
371
- </View>
372
-
373
- {/* Remote Device Management Section */}
374
- <View style={styles.usersContainer}>
375
- <View style={styles.sectionHeader}>
376
- <Text style={[styles.sectionTitle, { color: colors.text }]}>
377
- All Devices & Sessions
480
+ <ScrollView
481
+ style={styles.content}
482
+ showsVerticalScrollIndicator={false}
483
+ contentContainerStyle={styles.scrollContent}
484
+ >
485
+ {isLoading ? (
486
+ <View style={styles.loadingContainer}>
487
+ <ActivityIndicator size="large" color={colors.accent} />
488
+ <Text style={[styles.loadingText, { color: colors.secondaryText }]}>
489
+ Loading accounts...
378
490
  </Text>
379
- <TouchableOpacity
380
- style={[styles.toggleButton, { borderColor: colors.accent }]}
381
- onPress={() => setShowRemoteDevices(!showRemoteDevices)}
382
- >
383
- <Text style={[styles.toggleButtonText, { color: colors.accent }]}>
384
- {showRemoteDevices ? 'Hide' : 'Show'}
385
- </Text>
386
- </TouchableOpacity>
387
491
  </View>
388
-
389
- {showRemoteDevices && (
390
- <>
391
- {loadingDeviceSessions ? (
392
- <View style={styles.loadingContainer}>
393
- <ActivityIndicator color={colors.accent} size="small" />
394
- <Text style={[styles.loadingText, { color: colors.secondaryText }]}>
395
- Loading device sessions...
492
+ ) : (
493
+ <>
494
+ <Text style={[styles.sectionTitle, { color: colors.text }]}>
495
+ Saved Accounts ({sessionsWithUsers.length})
496
+ </Text>
497
+
498
+ {sessionsWithUsers.length === 0 ? (
499
+ <View style={styles.emptyState}>
500
+ <Text style={[styles.emptyText, { color: colors.secondaryText }]}>
501
+ No saved accounts found
502
+ </Text>
503
+ </View>
504
+ ) : (
505
+ sessionsWithUsers.map(renderSessionItem)
506
+ )}
507
+
508
+ <View style={styles.actionsSection}>
509
+ <TouchableOpacity
510
+ style={[styles.actionButton, {
511
+ borderColor: colors.border,
512
+ backgroundColor: colors.card,
513
+ }]}
514
+ onPress={() => navigate?.('SignIn')}
515
+ >
516
+ <Text style={[styles.actionButtonText, { color: colors.text }]}>
517
+ + Add Another Account
518
+ </Text>
519
+ </TouchableOpacity>
520
+
521
+ {sessionsWithUsers.length > 0 && (
522
+ <TouchableOpacity
523
+ style={[styles.actionButton, styles.dangerButton, {
524
+ borderColor: colors.destructive,
525
+ backgroundColor: colors.background,
526
+ }]}
527
+ onPress={handleLogoutAll}
528
+ >
529
+ <Text style={[styles.dangerButtonText, { color: colors.destructive }]}>
530
+ Sign Out All Accounts
396
531
  </Text>
397
- </View>
398
- ) : (
399
- <>
400
- {allDeviceSessions.length > 0 ? (
401
- <>
402
- {allDeviceSessions.map(renderDeviceSessionItem)}
403
- {allDeviceSessions.filter(s => !s.isCurrent).length > 0 && (
404
- <TouchableOpacity
405
- style={[styles.actionButton, { borderColor: colors.destructive, marginTop: 12 }]}
406
- onPress={handleLogoutAllDevices}
407
- >
408
- <Text style={[styles.actionButtonText, { color: colors.destructive }]}>
409
- Logout All Other Devices
410
- </Text>
411
- </TouchableOpacity>
412
- )}
413
- </>
414
- ) : (
415
- <Text style={[styles.emptyText, { color: colors.secondaryText }]}>
416
- No other device sessions found
417
- </Text>
418
- )}
419
- </>
532
+ </TouchableOpacity>
420
533
  )}
421
- </>
422
- )}
423
- </View>
534
+ </View>
424
535
 
425
- <View style={styles.actionsContainer}>
426
- <TouchableOpacity
427
- style={[styles.actionButton, { borderColor: colors.border }]}
428
- onPress={() => navigate('SignIn')}
429
- >
430
- <Text style={[styles.actionButtonText, { color: colors.text }]}>
431
- Add Another Account
432
- </Text>
433
- </TouchableOpacity>
536
+ {/* Device Management Section */}
537
+ <View style={styles.actionsSection}>
538
+ <TouchableOpacity
539
+ style={[styles.actionButton, {
540
+ borderColor: colors.border,
541
+ backgroundColor: colors.card,
542
+ }]}
543
+ onPress={() => setShowDeviceManagement(!showDeviceManagement)}
544
+ >
545
+ <Text style={[styles.actionButtonText, { color: colors.text }]}>
546
+ {showDeviceManagement ? '− Hide Device Management' : '+ Manage Device Sessions'}
547
+ </Text>
548
+ </TouchableOpacity>
549
+ </View>
434
550
 
435
- {sessions.length > 1 && (
436
- <TouchableOpacity
437
- style={[styles.dangerButton, { backgroundColor: isDarkTheme ? '#300000' : '#FFEBEE' }]}
438
- onPress={handleLogoutAll}
439
- disabled={isLoading}
440
- >
441
- {isLoading ? (
442
- <ActivityIndicator color={colors.destructive} size="small" />
443
- ) : (
444
- <Text style={[styles.dangerButtonText, { color: colors.destructive }]}>
445
- Sign Out All Sessions
551
+ {showDeviceManagement && (
552
+ <View style={[styles.deviceManagementSection, {
553
+ backgroundColor: colors.card,
554
+ borderColor: colors.border,
555
+ }]}>
556
+ <Text style={[styles.sectionTitle, { color: colors.text }]}>
557
+ Device Sessions
558
+ </Text>
559
+
560
+ {loadingDeviceSessions ? (
561
+ <View style={styles.loadingContainer}>
562
+ <ActivityIndicator size="large" color={colors.accent} />
563
+ <Text style={[styles.loadingText, { color: colors.secondaryText }]}>
564
+ Loading device sessions...
565
+ </Text>
566
+ </View>
567
+ ) : deviceSessions.length === 0 ? (
568
+ <View style={styles.emptyState}>
569
+ <Text style={[styles.emptyText, { color: colors.secondaryText }]}>
570
+ No device sessions found
571
+ </Text>
572
+ </View>
573
+ ) : (
574
+ <>
575
+ {deviceSessions.map(renderDeviceSessionItem)}
576
+
577
+ {deviceSessions.filter(session => !session.isCurrent).length > 0 && (
578
+ <TouchableOpacity
579
+ style={[styles.actionButton, styles.dangerButton, {
580
+ borderColor: colors.destructive,
581
+ backgroundColor: colors.background,
582
+ marginTop: 20,
583
+ }]}
584
+ onPress={handleLogoutAllDevices}
585
+ disabled={loggingOutAllDevices}
586
+ >
587
+ {loggingOutAllDevices ? (
588
+ <ActivityIndicator color={colors.destructive} size="small" />
589
+ ) : (
590
+ <Text style={[styles.dangerButtonText, { color: colors.destructive }]}>
591
+ Sign Out All Other Devices
592
+ </Text>
593
+ )}
594
+ </TouchableOpacity>
595
+ )}
596
+ </>
597
+ )}
598
+ </View>
599
+ )}
600
+
601
+ <View style={styles.actionsSection}>
602
+ <TouchableOpacity
603
+ style={[styles.actionButton, {
604
+ borderColor: colors.border,
605
+ backgroundColor: colors.card,
606
+ }]}
607
+ onPress={() => navigate?.('SignIn')}
608
+ >
609
+ <Text style={[styles.actionButtonText, { color: colors.text }]}>
610
+ + Add Another Account
446
611
  </Text>
612
+ </TouchableOpacity>
613
+
614
+ {sessionsWithUsers.length > 0 && (
615
+ <TouchableOpacity
616
+ style={[styles.actionButton, styles.dangerButton, {
617
+ borderColor: colors.destructive,
618
+ backgroundColor: colors.background,
619
+ }]}
620
+ onPress={handleLogoutAll}
621
+ >
622
+ <Text style={[styles.dangerButtonText, { color: colors.destructive }]}>
623
+ Sign Out All Accounts
624
+ </Text>
625
+ </TouchableOpacity>
447
626
  )}
448
- </TouchableOpacity>
449
- )}
450
- </View>
627
+ </View>
628
+ </>
629
+ )}
451
630
  </ScrollView>
452
631
  </View>
453
632
  );
@@ -462,168 +641,187 @@ const styles = StyleSheet.create({
462
641
  alignItems: 'center',
463
642
  justifyContent: 'space-between',
464
643
  paddingHorizontal: 20,
465
- paddingTop: Platform.OS === 'ios' ? 50 : 20,
466
- paddingBottom: 20,
467
- borderBottomWidth: 1,
468
- borderBottomColor: 'rgba(0, 0, 0, 0.1)',
644
+ paddingTop: 20,
645
+ paddingBottom: 10,
469
646
  },
470
647
  backButton: {
471
- paddingVertical: 8,
472
- paddingHorizontal: 4,
648
+ padding: 8,
473
649
  },
474
650
  backButtonText: {
475
651
  fontSize: 18,
476
- fontFamily: fontFamilies.phudu,
652
+ fontFamily: fontFamilies.phuduMedium,
477
653
  },
478
654
  title: {
479
- fontSize: 20,
480
- fontFamily: fontFamilies.phuduSemiBold,
655
+ fontSize: 24,
656
+ fontFamily: fontFamilies.phuduBold,
481
657
  textAlign: 'center',
482
658
  },
483
- placeholder: {
484
- width: 60, // Same as back button to center title
659
+ headerSpacer: {
660
+ width: 40,
485
661
  },
486
- scrollView: {
662
+ content: {
487
663
  flex: 1,
664
+ paddingHorizontal: 20,
488
665
  },
489
- scrollContainer: {
490
- padding: 20,
666
+ scrollContent: {
667
+ paddingBottom: 40,
491
668
  },
492
- description: {
493
- marginBottom: 24,
669
+ loadingContainer: {
670
+ flex: 1,
671
+ justifyContent: 'center',
672
+ alignItems: 'center',
673
+ paddingTop: 60,
494
674
  },
495
- descriptionText: {
496
- fontSize: 14,
675
+ loadingText: {
676
+ marginTop: 16,
677
+ fontSize: 16,
497
678
  fontFamily: fontFamilies.phudu,
498
- lineHeight: 20,
499
- },
500
- usersContainer: {
501
- marginBottom: 32,
502
679
  },
503
680
  sectionTitle: {
504
- fontSize: 16,
681
+ fontSize: 20,
505
682
  fontFamily: fontFamilies.phuduSemiBold,
506
- marginBottom: 16,
683
+ marginBottom: 20,
684
+ marginTop: 10,
685
+ },
686
+ emptyState: {
687
+ alignItems: 'center',
688
+ paddingVertical: 40,
507
689
  },
508
- userItem: {
690
+ emptyText: {
691
+ fontSize: 16,
692
+ fontFamily: fontFamilies.phudu,
693
+ textAlign: 'center',
694
+ },
695
+ sessionCard: {
696
+ borderRadius: 16,
697
+ marginBottom: 16,
698
+ padding: 20,
699
+ shadowOffset: {
700
+ width: 0,
701
+ height: 2,
702
+ },
703
+ shadowOpacity: 0.1,
704
+ shadowRadius: 8,
705
+ elevation: 3,
706
+ },
707
+ sessionHeader: {
509
708
  flexDirection: 'row',
510
709
  alignItems: 'center',
511
- justifyContent: 'space-between',
512
- padding: 16,
513
- borderRadius: 12,
514
- borderWidth: 1,
515
- marginBottom: 12,
710
+ marginBottom: 16,
711
+ },
712
+ avatarContainer: {
713
+ position: 'relative',
714
+ marginRight: 16,
715
+ },
716
+ avatar: {
717
+ width: 60,
718
+ height: 60,
719
+ borderRadius: 30,
720
+ },
721
+ avatarPlaceholder: {
722
+ width: 60,
723
+ height: 60,
724
+ borderRadius: 30,
725
+ justifyContent: 'center',
726
+ alignItems: 'center',
727
+ },
728
+ avatarText: {
729
+ fontSize: 24,
730
+ fontFamily: fontFamilies.phuduBold,
731
+ },
732
+ activeBadge: {
733
+ position: 'absolute',
734
+ bottom: -2,
735
+ right: -2,
736
+ width: 20,
737
+ height: 20,
738
+ borderRadius: 10,
739
+ justifyContent: 'center',
740
+ alignItems: 'center',
741
+ },
742
+ activeBadgeText: {
743
+ color: 'white',
744
+ fontSize: 12,
745
+ fontFamily: fontFamilies.phuduBold,
516
746
  },
517
747
  userInfo: {
518
748
  flex: 1,
749
+ justifyContent: 'center',
519
750
  },
520
- username: {
521
- fontSize: 16,
751
+ displayName: {
752
+ fontSize: 18,
522
753
  fontFamily: fontFamilies.phuduSemiBold,
523
754
  marginBottom: 4,
524
755
  },
525
- email: {
756
+ username: {
526
757
  fontSize: 14,
527
758
  fontFamily: fontFamilies.phudu,
528
- marginBottom: 8,
529
- },
530
- activeBadge: {
531
- paddingHorizontal: 8,
532
- paddingVertical: 4,
533
- borderRadius: 6,
534
- alignSelf: 'flex-start',
759
+ marginBottom: 4,
535
760
  },
536
- activeBadgeText: {
537
- color: '#FFFFFF',
761
+ lastActive: {
538
762
  fontSize: 12,
539
- fontFamily: fontFamilies.phuduMedium,
763
+ fontFamily: fontFamilies.phudu,
540
764
  },
541
- userActions: {
765
+ sessionActions: {
542
766
  flexDirection: 'row',
543
- gap: 8,
767
+ justifyContent: 'space-between',
768
+ gap: 12,
544
769
  },
545
770
  switchButton: {
546
- paddingHorizontal: 16,
547
- paddingVertical: 8,
548
- borderRadius: 8,
771
+ flex: 1,
772
+ paddingVertical: 12,
773
+ paddingHorizontal: 20,
549
774
  borderWidth: 1,
550
- minWidth: 70,
775
+ borderRadius: 8,
551
776
  alignItems: 'center',
777
+ justifyContent: 'center',
552
778
  },
553
779
  switchButtonText: {
554
780
  fontSize: 14,
555
- fontFamily: fontFamilies.phuduMedium,
781
+ fontFamily: fontFamilies.phuduSemiBold,
556
782
  },
557
783
  removeButton: {
558
- paddingHorizontal: 16,
559
- paddingVertical: 8,
560
- borderRadius: 8,
784
+ flex: 1,
785
+ paddingVertical: 12,
786
+ paddingHorizontal: 20,
561
787
  borderWidth: 1,
562
- minWidth: 70,
788
+ borderRadius: 8,
563
789
  alignItems: 'center',
790
+ justifyContent: 'center',
564
791
  },
565
792
  removeButtonText: {
566
793
  fontSize: 14,
567
- fontFamily: fontFamilies.phuduMedium,
794
+ fontFamily: fontFamilies.phuduSemiBold,
568
795
  },
569
- actionsContainer: {
796
+ actionsSection: {
797
+ marginTop: 40,
570
798
  gap: 16,
571
799
  },
572
800
  actionButton: {
573
801
  paddingVertical: 16,
574
802
  paddingHorizontal: 20,
575
- borderRadius: 12,
576
803
  borderWidth: 1,
804
+ borderRadius: 12,
577
805
  alignItems: 'center',
806
+ justifyContent: 'center',
578
807
  },
579
808
  actionButtonText: {
580
809
  fontSize: 16,
581
- fontFamily: fontFamilies.phuduMedium,
810
+ fontFamily: fontFamilies.phuduSemiBold,
582
811
  },
583
812
  dangerButton: {
584
- paddingVertical: 16,
585
- paddingHorizontal: 20,
586
- borderRadius: 12,
587
- alignItems: 'center',
813
+ // Additional styles for danger buttons if needed
588
814
  },
589
815
  dangerButtonText: {
590
816
  fontSize: 16,
591
- fontFamily: fontFamilies.phuduMedium,
592
- },
593
- sectionHeader: {
594
- flexDirection: 'row',
595
- alignItems: 'center',
596
- justifyContent: 'space-between',
597
- marginBottom: 16,
817
+ fontFamily: fontFamilies.phuduSemiBold,
598
818
  },
599
- toggleButton: {
600
- paddingHorizontal: 12,
601
- paddingVertical: 6,
602
- borderRadius: 6,
819
+ deviceManagementSection: {
820
+ marginTop: 20,
821
+ padding: 16,
822
+ borderRadius: 12,
603
823
  borderWidth: 1,
604
824
  },
605
- toggleButtonText: {
606
- fontSize: 14,
607
- fontFamily: fontFamilies.phuduMedium,
608
- },
609
- loadingContainer: {
610
- flexDirection: 'row',
611
- alignItems: 'center',
612
- justifyContent: 'center',
613
- paddingVertical: 20,
614
- gap: 8,
615
- },
616
- loadingText: {
617
- fontSize: 14,
618
- fontFamily: fontFamilies.phudu,
619
- },
620
- emptyText: {
621
- fontSize: 14,
622
- fontFamily: fontFamilies.phudu,
623
- textAlign: 'center',
624
- paddingVertical: 20,
625
- fontStyle: 'italic',
626
- },
627
825
  });
628
826
 
629
- export default AccountSwitcherScreen;
827
+ export default ModernAccountSwitcherScreen;