@oxyhq/services 5.11.9 → 5.11.10

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 (192) hide show
  1. package/lib/commonjs/ui/components/AnimationExample.js +213 -0
  2. package/lib/commonjs/ui/components/AnimationExample.js.map +1 -0
  3. package/lib/commonjs/ui/components/FollowButton.js +58 -47
  4. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  5. package/lib/commonjs/ui/components/GroupedItem.js +2 -1
  6. package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
  7. package/lib/commonjs/ui/components/GroupedSection.js +3 -0
  8. package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
  9. package/lib/commonjs/ui/components/Header.js +25 -11
  10. package/lib/commonjs/ui/components/Header.js.map +1 -1
  11. package/lib/commonjs/ui/components/OxyProvider.js +69 -33
  12. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  13. package/lib/commonjs/ui/components/ProfileCard.js +5 -1
  14. package/lib/commonjs/ui/components/ProfileCard.js.map +1 -1
  15. package/lib/commonjs/ui/components/index.js +0 -7
  16. package/lib/commonjs/ui/components/index.js.map +1 -1
  17. package/lib/commonjs/ui/components/internal/TextField.js +8 -4
  18. package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
  19. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +161 -0
  20. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
  21. package/lib/commonjs/ui/context/OxyContext.js +97 -38
  22. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  23. package/lib/commonjs/ui/hooks/useFollow.types.js +2 -0
  24. package/lib/commonjs/ui/hooks/useFollow.types.js.map +1 -0
  25. package/lib/commonjs/ui/navigation/OxyRouter.js +10 -0
  26. package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
  27. package/lib/commonjs/ui/screens/AccountCenterScreen.js +26 -14
  28. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  29. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +3 -3
  30. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  31. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +64 -15
  32. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  33. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +4 -4
  34. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  35. package/lib/commonjs/ui/screens/FeedbackScreen.js +72 -75
  36. package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -1
  37. package/lib/commonjs/ui/screens/FileManagementScreen.js +286 -126
  38. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  39. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +322 -0
  40. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -0
  41. package/lib/commonjs/ui/screens/ProfileScreen.js +1 -1
  42. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  43. package/lib/commonjs/ui/screens/SessionManagementScreen.js +176 -174
  44. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  45. package/lib/commonjs/ui/screens/SignInScreen.js +43 -52
  46. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  47. package/lib/commonjs/ui/screens/SignUpScreen.js +6 -4
  48. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  49. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +386 -0
  50. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -0
  51. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +25 -15
  52. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  53. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +16 -9
  54. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  55. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +1 -1
  56. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  57. package/lib/commonjs/ui/styles/authStyles.js +1 -1
  58. package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
  59. package/lib/module/ui/components/AnimationExample.js +209 -0
  60. package/lib/module/ui/components/AnimationExample.js.map +1 -0
  61. package/lib/module/ui/components/FollowButton.js +58 -47
  62. package/lib/module/ui/components/FollowButton.js.map +1 -1
  63. package/lib/module/ui/components/GroupedItem.js +2 -1
  64. package/lib/module/ui/components/GroupedItem.js.map +1 -1
  65. package/lib/module/ui/components/GroupedSection.js +3 -0
  66. package/lib/module/ui/components/GroupedSection.js.map +1 -1
  67. package/lib/module/ui/components/Header.js +25 -11
  68. package/lib/module/ui/components/Header.js.map +1 -1
  69. package/lib/module/ui/components/OxyProvider.js +70 -34
  70. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  71. package/lib/module/ui/components/ProfileCard.js +5 -1
  72. package/lib/module/ui/components/ProfileCard.js.map +1 -1
  73. package/lib/module/ui/components/index.js +0 -1
  74. package/lib/module/ui/components/index.js.map +1 -1
  75. package/lib/module/ui/components/internal/TextField.js +8 -4
  76. package/lib/module/ui/components/internal/TextField.js.map +1 -1
  77. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +156 -0
  78. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
  79. package/lib/module/ui/context/OxyContext.js +97 -39
  80. package/lib/module/ui/context/OxyContext.js.map +1 -1
  81. package/lib/module/ui/hooks/useFollow.types.js +2 -0
  82. package/lib/module/ui/hooks/useFollow.types.js.map +1 -0
  83. package/lib/module/ui/navigation/OxyRouter.js +10 -0
  84. package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
  85. package/lib/module/ui/screens/AccountCenterScreen.js +12 -1
  86. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  87. package/lib/module/ui/screens/AccountOverviewScreen.js +3 -3
  88. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  89. package/lib/module/ui/screens/AccountSettingsScreen.js +64 -15
  90. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  91. package/lib/module/ui/screens/AccountSwitcherScreen.js +4 -4
  92. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  93. package/lib/module/ui/screens/FeedbackScreen.js +72 -75
  94. package/lib/module/ui/screens/FeedbackScreen.js.map +1 -1
  95. package/lib/module/ui/screens/FileManagementScreen.js +285 -125
  96. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  97. package/lib/module/ui/screens/LanguageSelectorScreen.js +319 -0
  98. package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -0
  99. package/lib/module/ui/screens/ProfileScreen.js +1 -1
  100. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  101. package/lib/module/ui/screens/SessionManagementScreen.js +177 -175
  102. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  103. package/lib/module/ui/screens/SignInScreen.js +44 -53
  104. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  105. package/lib/module/ui/screens/SignUpScreen.js +6 -4
  106. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  107. package/lib/module/ui/screens/WelcomeNewUserScreen.js +382 -0
  108. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -0
  109. package/lib/module/ui/screens/internal/SignInPasswordStep.js +23 -14
  110. package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  111. package/lib/module/ui/screens/internal/SignInUsernameStep.js +15 -9
  112. package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  113. package/lib/module/ui/screens/karma/KarmaCenterScreen.js +1 -1
  114. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  115. package/lib/module/ui/styles/authStyles.js +1 -1
  116. package/lib/module/ui/styles/authStyles.js.map +1 -1
  117. package/lib/typescript/models/interfaces.d.ts +1 -5
  118. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  119. package/lib/typescript/models/session.d.ts +1 -4
  120. package/lib/typescript/models/session.d.ts.map +1 -1
  121. package/lib/typescript/ui/components/AnimationExample.d.ts +4 -0
  122. package/lib/typescript/ui/components/AnimationExample.d.ts.map +1 -0
  123. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  124. package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
  125. package/lib/typescript/ui/components/Header.d.ts +9 -0
  126. package/lib/typescript/ui/components/Header.d.ts.map +1 -1
  127. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  128. package/lib/typescript/ui/components/ProfileCard.d.ts +1 -3
  129. package/lib/typescript/ui/components/ProfileCard.d.ts.map +1 -1
  130. package/lib/typescript/ui/components/index.d.ts +0 -1
  131. package/lib/typescript/ui/components/index.d.ts.map +1 -1
  132. package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
  133. package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts +27 -0
  134. package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -0
  135. package/lib/typescript/ui/context/OxyContext.d.ts +6 -2
  136. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  137. package/lib/typescript/ui/hooks/useFollow.types.d.ts +33 -0
  138. package/lib/typescript/ui/hooks/useFollow.types.d.ts.map +1 -0
  139. package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
  140. package/lib/typescript/ui/navigation/types.d.ts +5 -0
  141. package/lib/typescript/ui/navigation/types.d.ts.map +1 -1
  142. package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  143. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  144. package/lib/typescript/ui/screens/FeedbackScreen.d.ts.map +1 -1
  145. package/lib/typescript/ui/screens/FileManagementScreen.d.ts +18 -1
  146. package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
  147. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +7 -0
  148. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -0
  149. package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
  150. package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  151. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  152. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  153. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts +13 -0
  154. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -0
  155. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts +5 -5
  156. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
  157. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts +4 -4
  158. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts.map +1 -1
  159. package/lib/typescript/ui/styles/authStyles.d.ts +1 -1
  160. package/package.json +10 -2
  161. package/src/models/interfaces.ts +2 -5
  162. package/src/models/session.ts +1 -4
  163. package/src/ui/components/AnimationExample.tsx +194 -0
  164. package/src/ui/components/FollowButton.tsx +65 -45
  165. package/src/ui/components/GroupedItem.tsx +1 -0
  166. package/src/ui/components/GroupedSection.tsx +1 -1
  167. package/src/ui/components/Header.tsx +36 -12
  168. package/src/ui/components/OxyProvider.tsx +66 -32
  169. package/src/ui/components/ProfileCard.tsx +6 -8
  170. package/src/ui/components/index.ts +0 -1
  171. package/src/ui/components/internal/TextField.tsx +12 -6
  172. package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +158 -0
  173. package/src/ui/context/OxyContext.tsx +84 -54
  174. package/src/ui/hooks/useFollow.types.ts +33 -0
  175. package/src/ui/navigation/OxyRouter.tsx +10 -0
  176. package/src/ui/navigation/types.ts +6 -0
  177. package/src/ui/screens/AccountCenterScreen.tsx +13 -7
  178. package/src/ui/screens/AccountOverviewScreen.tsx +3 -3
  179. package/src/ui/screens/AccountSettingsScreen.tsx +65 -13
  180. package/src/ui/screens/AccountSwitcherScreen.tsx +4 -4
  181. package/src/ui/screens/FeedbackScreen.tsx +57 -80
  182. package/src/ui/screens/FileManagementScreen.tsx +278 -175
  183. package/src/ui/screens/LanguageSelectorScreen.tsx +322 -0
  184. package/src/ui/screens/ProfileScreen.tsx +6 -1
  185. package/src/ui/screens/SessionManagementScreen.tsx +148 -151
  186. package/src/ui/screens/SignInScreen.tsx +43 -62
  187. package/src/ui/screens/SignUpScreen.tsx +3 -5
  188. package/src/ui/screens/WelcomeNewUserScreen.tsx +272 -0
  189. package/src/ui/screens/internal/SignInPasswordStep.tsx +28 -13
  190. package/src/ui/screens/internal/SignInUsernameStep.tsx +21 -11
  191. package/src/ui/screens/karma/KarmaCenterScreen.tsx +1 -1
  192. package/src/ui/styles/authStyles.ts +1 -1
@@ -13,23 +13,22 @@ import {
13
13
  } from 'react-native';
14
14
  import type { BaseScreenProps } from '../navigation/types';
15
15
  import { useOxy } from '../context/OxyContext';
16
- import { fontFamilies } from '../styles/fonts';
17
16
  import { toast } from '../../lib/sonner';
18
- import { Ionicons } from '@expo/vector-icons';
19
- import OxyIcon from '../components/icon/OxyIcon';
20
17
  import type { ClientSession } from '../../models/session';
21
18
  import { confirmAction } from '../utils/confirmAction';
22
- import { Header } from '../components';
19
+ import { Header, GroupedSection } from '../components';
23
20
 
24
21
  const SessionManagementScreen: React.FC<BaseScreenProps> = ({
25
22
  onClose,
26
23
  theme,
27
24
  goBack,
28
25
  }) => {
29
- const { sessions: userSessions, activeSessionId, refreshSessions, logout, logoutAll, oxyServices } = useOxy();
26
+ const { sessions: userSessions, activeSessionId, refreshSessions, logout, logoutAll, switchSession } = useOxy();
30
27
  const [loading, setLoading] = useState(true);
31
28
  const [refreshing, setRefreshing] = useState(false);
32
29
  const [actionLoading, setActionLoading] = useState<string | null>(null);
30
+ const [switchLoading, setSwitchLoading] = useState<string | null>(null);
31
+ const [lastRefreshed, setLastRefreshed] = useState<Date | null>(null);
33
32
 
34
33
  const isDarkTheme = theme === 'dark';
35
34
  const textColor = isDarkTheme ? '#FFFFFF' : '#000000';
@@ -49,6 +48,7 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
49
48
  }
50
49
 
51
50
  await refreshSessions();
51
+ setLastRefreshed(new Date());
52
52
  } catch (error) {
53
53
  console.error('Failed to load sessions:', error);
54
54
  if (Platform.OS === 'web') {
@@ -126,33 +126,36 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
126
126
  );
127
127
  };
128
128
 
129
- const formatDate = (dateString: string) => {
129
+ // Relative time formatter (past & future)
130
+ const formatRelative = (dateString?: string) => {
131
+ if (!dateString) return 'Unknown';
130
132
  const date = new Date(dateString);
131
133
  const now = new Date();
132
- const diffInMinutes = Math.floor((now.getTime() - date.getTime()) / (1000 * 60));
133
-
134
- if (diffInMinutes < 1) return 'Just now';
135
- if (diffInMinutes < 60) return `${diffInMinutes}m ago`;
136
- if (diffInMinutes < 1440) return `${Math.floor(diffInMinutes / 60)}h ago`;
137
- if (diffInMinutes < 10080) return `${Math.floor(diffInMinutes / 1440)}d ago`;
138
-
134
+ const diffMs = date.getTime() - now.getTime();
135
+ const absMin = Math.abs(diffMs) / 60000;
136
+ const isFuture = diffMs > 0;
137
+ const fmt = (n: number) => (n < 1 ? 'moments' : Math.floor(n));
138
+ if (absMin < 1) return isFuture ? 'in moments' : 'just now';
139
+ if (absMin < 60) return isFuture ? `in ${fmt(absMin)}m` : `${fmt(absMin)}m ago`;
140
+ const hrs = absMin / 60;
141
+ if (hrs < 24) return isFuture ? `in ${fmt(hrs)}h` : `${fmt(hrs)}h ago`;
142
+ const days = hrs / 24;
143
+ if (days < 7) return isFuture ? `in ${fmt(days)}d` : `${fmt(days)}d ago`;
139
144
  return date.toLocaleDateString();
140
145
  };
141
146
 
142
- const getDeviceIcon = (deviceType: string, platform: string) => {
143
- if (platform.toLowerCase().includes('ios') || platform.toLowerCase().includes('iphone')) {
144
- return '📱';
145
- }
146
- if (platform.toLowerCase().includes('android')) {
147
- return '📱';
148
- }
149
- if (deviceType.toLowerCase().includes('mobile')) {
150
- return '📱';
151
- }
152
- if (deviceType.toLowerCase().includes('tablet')) {
153
- return '📱';
147
+ const handleSwitchSession = async (sessionId: string) => {
148
+ if (sessionId === activeSessionId) return;
149
+ setSwitchLoading(sessionId);
150
+ try {
151
+ await switchSession(sessionId);
152
+ toast.success('Switched session');
153
+ } catch (e) {
154
+ console.error('Switch session failed', e);
155
+ toast.error('Failed to switch session');
156
+ } finally {
157
+ setSwitchLoading(null);
154
158
  }
155
- return '💻'; // Desktop/web
156
159
  };
157
160
 
158
161
  useEffect(() => {
@@ -168,6 +171,85 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
168
171
  );
169
172
  }
170
173
 
174
+ // Build grouped section items for sessions
175
+ const sessionItems = userSessions.map((session: ClientSession) => {
176
+ const isCurrent = session.sessionId === activeSessionId;
177
+ const subtitleParts: string[] = [];
178
+ if (session.deviceId) subtitleParts.push(`Device ${session.deviceId.substring(0, 10)}...`);
179
+ subtitleParts.push(`Last ${formatRelative(session.lastActive)}`);
180
+ subtitleParts.push(`Expires ${formatRelative(session.expiresAt)}`);
181
+
182
+ return {
183
+ id: session.sessionId,
184
+ icon: isCurrent ? 'shield-checkmark' : 'laptop-outline',
185
+ iconColor: isCurrent ? successColor : primaryColor,
186
+ title: isCurrent ? 'Current Session' : `Session ${session.sessionId.substring(0, 8)}...`,
187
+ subtitle: subtitleParts.join(' \u2022 '),
188
+ showChevron: false,
189
+ multiRow: true,
190
+ customContentBelow: !isCurrent ? (
191
+ <View style={styles.sessionActionsRow}>
192
+ <TouchableOpacity
193
+ onPress={() => handleSwitchSession(session.sessionId)}
194
+ style={[styles.sessionPillButton, { backgroundColor: isDarkTheme ? '#1E2A38' : '#E6F2FF', borderColor: primaryColor }]}
195
+ disabled={switchLoading === session.sessionId || actionLoading === session.sessionId}
196
+ >
197
+ {switchLoading === session.sessionId ? (
198
+ <ActivityIndicator size="small" color={primaryColor} />
199
+ ) : (
200
+ <Text style={[styles.sessionPillText, { color: primaryColor }]}>Switch</Text>
201
+ )}
202
+ </TouchableOpacity>
203
+ <TouchableOpacity
204
+ onPress={() => handleLogoutSession(session.sessionId)}
205
+ style={[styles.sessionPillButton, { backgroundColor: isDarkTheme ? '#3A1E1E' : '#FFEBEE', borderColor: dangerColor }]}
206
+ disabled={actionLoading === session.sessionId || switchLoading === session.sessionId}
207
+ >
208
+ {actionLoading === session.sessionId ? (
209
+ <ActivityIndicator size="small" color={dangerColor} />
210
+ ) : (
211
+ <Text style={[styles.sessionPillText, { color: dangerColor }]}>Logout</Text>
212
+ )}
213
+ </TouchableOpacity>
214
+ </View>
215
+ ) : (
216
+ <View style={styles.sessionActionsRow}>
217
+ <Text style={[styles.currentBadgeText, { color: successColor }]}>Active</Text>
218
+ </View>
219
+ ),
220
+ selected: isCurrent,
221
+ dense: true,
222
+ };
223
+ });
224
+
225
+ // Bulk actions as grouped section items
226
+ const bulkItems = [
227
+ {
228
+ id: 'logout-others',
229
+ icon: 'exit-outline',
230
+ iconColor: primaryColor,
231
+ title: 'Logout Other Sessions',
232
+ subtitle: userSessions.filter(s => s.sessionId !== activeSessionId).length === 0 ? 'No other sessions' : 'End all sessions except this one',
233
+ onPress: handleLogoutOtherSessions,
234
+ showChevron: false,
235
+ customContent: actionLoading === 'others' ? <ActivityIndicator size="small" color={primaryColor} /> : undefined,
236
+ disabled: actionLoading === 'others' || userSessions.filter(s => s.sessionId !== activeSessionId).length === 0,
237
+ dense: true,
238
+ },
239
+ {
240
+ id: 'logout-all',
241
+ icon: 'warning-outline',
242
+ iconColor: dangerColor,
243
+ title: 'Logout All Sessions',
244
+ subtitle: 'End all sessions including this one',
245
+ onPress: handleLogoutAllSessions,
246
+ showChevron: false,
247
+ customContent: actionLoading === 'all' ? <ActivityIndicator size="small" color={dangerColor} /> : undefined,
248
+ disabled: actionLoading === 'all',
249
+ dense: true,
250
+ },
251
+ ];
252
+
171
253
  return (
172
254
  <View style={[styles.container, { backgroundColor }]}>
173
255
  <Header
@@ -190,72 +272,15 @@ const SessionManagementScreen: React.FC<BaseScreenProps> = ({
190
272
  >
191
273
  {userSessions.length > 0 ? (
192
274
  <>
193
- {userSessions.map((session: ClientSession) => (
194
- <View
195
- key={session.sessionId}
196
- style={[
197
- styles.sessionCard,
198
- {
199
- backgroundColor: secondaryBackgroundColor,
200
- borderColor,
201
- borderLeftColor: session.sessionId === activeSessionId ? successColor : borderColor,
202
- },
203
- ]}
204
- >
205
- <View style={styles.sessionHeader}>
206
- <View style={styles.sessionTitleRow}>
207
- <Text style={styles.deviceIcon}>💻</Text>
208
- <View style={styles.sessionTitleText}>
209
- <Text style={[styles.deviceName, { color: textColor }]}>Session {session.sessionId.substring(0, 8)}...</Text>
210
- {session.sessionId === activeSessionId && (
211
- <Text style={[styles.currentBadge, { color: successColor }]}>Current Session</Text>
212
- )}
213
- </View>
214
- </View>
215
- </View>
216
- <View style={styles.sessionDetails}>
217
- <Text style={[styles.sessionDetail, { color: isDarkTheme ? '#BBBBBB' : '#666666' }]}>Device ID: {session.deviceId ? session.deviceId.substring(0, 12) + '...' : 'Unknown'}</Text>
218
- <Text style={[styles.sessionDetail, { color: isDarkTheme ? '#BBBBBB' : '#666666' }]}>Last active: {session.lastActive ? new Date(session.lastActive).toLocaleDateString() : 'Unknown'}</Text>
219
- <Text style={[styles.sessionDetail, { color: isDarkTheme ? '#BBBBBB' : '#666666' }]}>Expires: {session.expiresAt ? new Date(session.expiresAt).toLocaleDateString() : 'Unknown'}</Text>
220
- </View>
221
- {session.sessionId !== activeSessionId && (
222
- <TouchableOpacity
223
- style={[styles.logoutButton, { backgroundColor: isDarkTheme ? '#400000' : '#FFEBEE' }]}
224
- onPress={() => handleLogoutSession(session.sessionId)}
225
- disabled={actionLoading === session.sessionId}
226
- >
227
- {actionLoading === session.sessionId ? (
228
- <ActivityIndicator size="small" color={dangerColor} />
229
- ) : (
230
- <Text style={[styles.logoutButtonText, { color: dangerColor }]}>Logout</Text>
231
- )}
232
- </TouchableOpacity>
233
- )}
234
- </View>
235
- ))}
236
- <View style={styles.bulkActions}>
237
- <TouchableOpacity
238
- style={[styles.bulkActionButton, { backgroundColor: isDarkTheme ? '#1A1A1A' : '#F0F0F0', borderColor }]}
239
- onPress={handleLogoutOtherSessions}
240
- disabled={actionLoading === 'others' || userSessions.filter(s => s.sessionId !== activeSessionId).length === 0}
241
- >
242
- {actionLoading === 'others' ? (
243
- <ActivityIndicator size="small" color={primaryColor} />
244
- ) : (
245
- <Text style={[styles.bulkActionButtonText, { color: textColor }]}>Logout Other Sessions</Text>
246
- )}
247
- </TouchableOpacity>
248
- <TouchableOpacity
249
- style={[styles.bulkActionButton, styles.dangerButton, { backgroundColor: isDarkTheme ? '#400000' : '#FFEBEE' }]}
250
- onPress={handleLogoutAllSessions}
251
- disabled={actionLoading === 'all'}
252
- >
253
- {actionLoading === 'all' ? (
254
- <ActivityIndicator size="small" color={dangerColor} />
255
- ) : (
256
- <Text style={[styles.bulkActionButtonText, { color: dangerColor }]}>Logout All Sessions</Text>
257
- )}
258
- </TouchableOpacity>
275
+ {lastRefreshed && (
276
+ <Text style={[styles.metaText, { color: isDarkTheme ? '#777' : '#777', marginBottom: 6 }]}>Last refreshed {formatRelative(lastRefreshed.toISOString())}</Text>
277
+ )}
278
+ <View style={styles.fullBleed}>
279
+ <GroupedSection items={sessionItems} theme={theme as 'light' | 'dark'} />
280
+ </View>
281
+ <View style={{ height: 12 }} />
282
+ <View style={styles.fullBleed}>
283
+ <GroupedSection items={bulkItems} theme={theme as 'light' | 'dark'} />
259
284
  </View>
260
285
  </>
261
286
  ) : (
@@ -290,74 +315,46 @@ const styles = StyleSheet.create({
290
315
  padding: 20,
291
316
  paddingTop: 0,
292
317
  },
293
- sessionCard: {
294
- borderRadius: 12,
295
- borderWidth: 1,
296
- borderLeftWidth: 4,
297
- padding: 16,
298
- marginBottom: 12,
299
- },
300
- sessionHeader: {
301
- marginBottom: 12,
318
+ // Removed legacy session card & bulk action styles (now using GroupedSection)
319
+ sessionActionsRow: {
320
+ flexDirection: 'row',
321
+ gap: 8,
322
+ marginTop: 6,
302
323
  },
303
- sessionTitleRow: {
324
+ sessionPillButton: {
325
+ paddingHorizontal: 14,
326
+ paddingVertical: 6,
327
+ borderRadius: 20,
328
+ borderWidth: 1,
304
329
  flexDirection: 'row',
305
330
  alignItems: 'center',
306
331
  },
307
- deviceIcon: {
308
- fontSize: 20,
309
- marginRight: 12,
310
- },
311
- sessionTitleText: {
312
- flex: 1,
313
- },
314
- deviceName: {
315
- fontSize: 16,
332
+ sessionPillText: {
333
+ fontSize: 12,
316
334
  fontWeight: '600',
317
- marginBottom: 2,
335
+ letterSpacing: 0.3,
336
+ textTransform: 'uppercase',
318
337
  },
319
- currentBadge: {
338
+ currentBadgeText: {
320
339
  fontSize: 12,
321
- fontWeight: '500',
322
- },
323
- sessionDetails: {
324
- marginBottom: 12,
325
- },
326
- sessionDetail: {
327
- fontSize: 14,
328
- marginBottom: 2,
329
- },
330
- logoutButton: {
331
- paddingVertical: 8,
332
- paddingHorizontal: 16,
333
- borderRadius: 6,
334
- alignItems: 'center',
335
- alignSelf: 'flex-start',
336
- },
337
- logoutButtonText: {
338
- fontSize: 14,
339
- fontWeight: '500',
340
- },
341
- bulkActions: {
342
- marginTop: 20,
343
- paddingTop: 20,
344
- borderTopWidth: 1,
345
- borderTopColor: '#E0E0E0',
346
- },
347
- bulkActionButton: {
348
- paddingVertical: 12,
349
- paddingHorizontal: 20,
350
- borderRadius: 8,
351
- borderWidth: 1,
352
- alignItems: 'center',
353
- marginBottom: 12,
340
+ fontWeight: '600',
341
+ paddingHorizontal: 10,
342
+ paddingVertical: 4,
343
+ backgroundColor: '#2E7D3215',
344
+ borderRadius: 16,
345
+ overflow: 'hidden',
346
+ textTransform: 'uppercase',
347
+ letterSpacing: 0.5,
354
348
  },
355
- dangerButton: {
356
- borderColor: 'transparent',
349
+ metaText: {
350
+ fontSize: 12,
351
+ textTransform: 'uppercase',
352
+ letterSpacing: 0.5,
353
+ fontWeight: '600',
357
354
  },
358
- bulkActionButtonText: {
359
- fontSize: 16,
360
- fontWeight: '500',
355
+ fullBleed: {
356
+ width: '100%',
357
+ alignSelf: 'stretch',
361
358
  },
362
359
  emptyState: {
363
360
  alignItems: 'center',
@@ -11,11 +11,19 @@ import {
11
11
  KeyboardAvoidingView,
12
12
  ScrollView,
13
13
  TextStyle,
14
- Animated,
15
14
  Dimensions,
16
15
  StatusBar,
17
16
  Alert,
18
17
  } from 'react-native';
18
+ import Animated, {
19
+ useSharedValue,
20
+ useAnimatedStyle,
21
+ withSpring,
22
+ withTiming,
23
+ interpolate,
24
+ runOnJS,
25
+ Easing,
26
+ } from 'react-native-reanimated';
19
27
  import type { BaseScreenProps } from '../navigation/types';
20
28
  import { useOxy } from '../context/OxyContext';
21
29
  import { fontFamilies, useThemeColors, createCommonStyles, createAuthStyles } from '../styles';
@@ -61,11 +69,12 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
61
69
  // Cache for validation results to prevent repeated API calls
62
70
  const validationCache = useRef<Map<string, { profile: any; timestamp: number }>>(new Map());
63
71
 
64
- const fadeAnim = useRef(new Animated.Value(1)).current;
65
- const slideAnim = useRef(new Animated.Value(0)).current;
66
- const scaleAnim = useRef(new Animated.Value(1)).current;
67
- const logoAnim = useRef(new Animated.Value(0)).current;
68
- const progressAnim = useRef(new Animated.Value(initialStep ? 1.0 : 0.5)).current;
72
+ // Reanimated shared values
73
+ const fadeAnim = useSharedValue(1);
74
+ const slideAnim = useSharedValue(0);
75
+ const scaleAnim = useSharedValue(1);
76
+ const logoAnim = useSharedValue(0);
77
+ const progressAnim = useSharedValue(initialStep ? 1.0 : 0.5);
69
78
 
70
79
  const { login, isLoading, user, isAuthenticated, sessions, oxyServices } = useOxy();
71
80
 
@@ -83,12 +92,10 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
83
92
 
84
93
  // Initialize logo animation
85
94
  useEffect(() => {
86
- Animated.spring(logoAnim, {
87
- toValue: 1,
88
- tension: 50,
89
- friction: 8,
90
- useNativeDriver: Platform.OS !== 'web',
91
- }).start();
95
+ logoAnim.value = withSpring(1, {
96
+ damping: 15,
97
+ stiffness: 150,
98
+ });
92
99
  }, [logoAnim]);
93
100
 
94
101
  // Input focus handlers (no animation)
@@ -231,56 +238,35 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
231
238
  // Animation functions
232
239
  const animateTransition = useCallback((nextStep: number) => {
233
240
  // Scale down current content
234
- Animated.timing(scaleAnim, {
235
- toValue: 0.95,
236
- duration: 150,
237
- useNativeDriver: Platform.OS !== 'web',
238
- }).start();
239
-
240
- // Fade out
241
- Animated.timing(fadeAnim, {
242
- toValue: 0,
243
- duration: 200,
244
- useNativeDriver: Platform.OS !== 'web',
245
- }).start(() => {
246
- setCurrentStep(nextStep);
247
-
248
- // Reset animations
249
- slideAnim.setValue(-50);
250
- scaleAnim.setValue(0.95);
251
-
252
- // Animate in new content
253
- Animated.parallel([
254
- Animated.timing(fadeAnim, {
255
- toValue: 1,
256
- duration: 300,
257
- useNativeDriver: Platform.OS !== 'web',
258
- }),
259
- Animated.spring(slideAnim, {
260
- toValue: 0,
261
- tension: 80,
262
- friction: 8,
263
- useNativeDriver: Platform.OS !== 'web',
264
- }),
265
- Animated.spring(scaleAnim, {
266
- toValue: 1,
267
- tension: 80,
268
- friction: 8,
269
- useNativeDriver: Platform.OS !== 'web',
270
- })
271
- ]).start();
241
+ scaleAnim.value = withTiming(0.95, { duration: 150 });
242
+
243
+ // Fade out and then animate in new content
244
+ fadeAnim.value = withTiming(0, { duration: 200 }, (finished) => {
245
+ if (finished) {
246
+ runOnJS(setCurrentStep)(nextStep);
247
+
248
+ // Reset animations
249
+ slideAnim.value = -50;
250
+ scaleAnim.value = 0.95;
251
+
252
+ // Animate in new content
253
+ fadeAnim.value = withTiming(1, { duration: 300 });
254
+ slideAnim.value = withSpring(0, {
255
+ damping: 15,
256
+ stiffness: 200,
257
+ });
258
+ scaleAnim.value = withSpring(1, {
259
+ damping: 15,
260
+ stiffness: 200,
261
+ });
262
+ }
272
263
  });
273
264
  }, [fadeAnim, slideAnim, scaleAnim]);
274
265
 
275
266
  const nextStep = useCallback(() => {
276
267
  if (currentStep < 1) {
277
268
  // Animate progress bar
278
- Animated.timing(progressAnim, {
279
- toValue: 1.0,
280
- duration: 300,
281
- useNativeDriver: false,
282
- }).start();
283
-
269
+ progressAnim.value = withTiming(1.0, { duration: 300 });
284
270
  animateTransition(currentStep + 1);
285
271
  }
286
272
  }, [currentStep, progressAnim, animateTransition]);
@@ -288,12 +274,7 @@ const SignInScreen: React.FC<BaseScreenProps> = ({
288
274
  const prevStep = useCallback(() => {
289
275
  if (currentStep > 0) {
290
276
  // Animate progress bar
291
- Animated.timing(progressAnim, {
292
- toValue: 0.5,
293
- duration: 300,
294
- useNativeDriver: false,
295
- }).start();
296
-
277
+ progressAnim.value = withTiming(0.5, { duration: 300 });
297
278
  animateTransition(currentStep - 1);
298
279
  }
299
280
  }, [currentStep, progressAnim, animateTransition]);
@@ -730,11 +730,9 @@ const SignUpScreen: React.FC<BaseScreenProps> = ({
730
730
  const user = await signUp(formData.username, formData.email, formData.password);
731
731
  toast.success('Account created successfully! Welcome to Oxy!');
732
732
 
733
- if (onAuthenticated) {
734
- onAuthenticated(user);
735
- }
736
-
737
- resetForm();
733
+ // Instead of finalizing immediately, route to post-signup welcome & avatar setup
734
+ navigate('WelcomeNewUser', { newUser: user });
735
+ resetForm(); // Clear the form for potential future use
738
736
  } catch (error: any) {
739
737
  toast.error(error.message || 'Sign up failed');
740
738
  }