@oxyhq/services 5.3.10 → 5.3.11

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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +63 -571
  3. package/lib/commonjs/core/index.js +103 -34
  4. package/lib/commonjs/core/index.js.map +1 -1
  5. package/lib/commonjs/lib/sonner.js +17 -0
  6. package/lib/commonjs/lib/sonner.js.map +1 -0
  7. package/lib/commonjs/lib/sonner.web.js +17 -0
  8. package/lib/commonjs/lib/sonner.web.js.map +1 -0
  9. package/lib/commonjs/ui/components/FollowButton.js +54 -4
  10. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  11. package/lib/commonjs/ui/components/OxyProvider.js +22 -3
  12. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  13. package/lib/commonjs/ui/screens/AccountCenterScreen.js +4 -3
  14. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  15. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +15 -14
  16. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  17. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +10 -9
  18. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  19. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +17 -16
  20. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  21. package/lib/commonjs/ui/screens/AppInfoScreen.js +4 -5
  22. package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
  23. package/lib/commonjs/ui/screens/SessionManagementScreen.js +7 -6
  24. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  25. package/lib/commonjs/ui/screens/SignInScreen.js +821 -74
  26. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  27. package/lib/commonjs/ui/screens/SignUpScreen.js +7 -5
  28. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  29. package/lib/commonjs/ui/styles/theme.js +1 -1
  30. package/lib/module/core/index.js +103 -34
  31. package/lib/module/core/index.js.map +1 -1
  32. package/lib/module/lib/sonner.js +4 -0
  33. package/lib/module/lib/sonner.js.map +1 -0
  34. package/lib/module/lib/sonner.web.js +4 -0
  35. package/lib/module/lib/sonner.web.js.map +1 -0
  36. package/lib/module/ui/components/FollowButton.js +54 -4
  37. package/lib/module/ui/components/FollowButton.js.map +1 -1
  38. package/lib/module/ui/components/OxyProvider.js +22 -3
  39. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  40. package/lib/module/ui/screens/AccountCenterScreen.js +4 -3
  41. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  42. package/lib/module/ui/screens/AccountOverviewScreen.js +15 -14
  43. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  44. package/lib/module/ui/screens/AccountSettingsScreen.js +10 -9
  45. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  46. package/lib/module/ui/screens/AccountSwitcherScreen.js +17 -16
  47. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  48. package/lib/module/ui/screens/AppInfoScreen.js +5 -6
  49. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  50. package/lib/module/ui/screens/SessionManagementScreen.js +7 -6
  51. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  52. package/lib/module/ui/screens/SignInScreen.js +824 -77
  53. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  54. package/lib/module/ui/screens/SignUpScreen.js +7 -5
  55. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  56. package/lib/module/ui/styles/theme.js +1 -1
  57. package/lib/typescript/core/index.d.ts +44 -23
  58. package/lib/typescript/core/index.d.ts.map +1 -1
  59. package/lib/typescript/lib/sonner.d.ts +2 -0
  60. package/lib/typescript/lib/sonner.d.ts.map +1 -0
  61. package/lib/typescript/lib/sonner.web.d.ts +2 -0
  62. package/lib/typescript/lib/sonner.web.d.ts.map +1 -0
  63. package/lib/typescript/models/interfaces.d.ts +24 -0
  64. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  65. package/lib/typescript/ui/components/FollowButton.d.ts +32 -1
  66. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  67. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  68. package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  69. package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
  70. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  71. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  72. package/lib/typescript/ui/screens/AppInfoScreen.d.ts.map +1 -1
  73. package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  74. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  75. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  76. package/package.json +5 -3
  77. package/src/core/index.ts +104 -36
  78. package/src/lib/sonner.ts +1 -0
  79. package/src/lib/sonner.web.ts +1 -0
  80. package/src/models/interfaces.ts +29 -0
  81. package/src/ui/components/FollowButton.tsx +67 -3
  82. package/src/ui/components/OxyProvider.tsx +15 -0
  83. package/src/ui/screens/AccountCenterScreen.tsx +4 -3
  84. package/src/ui/screens/AccountOverviewScreen.tsx +15 -14
  85. package/src/ui/screens/AccountSettingsScreen.tsx +10 -9
  86. package/src/ui/screens/AccountSwitcherScreen.tsx +17 -16
  87. package/src/ui/screens/AppInfoScreen.tsx +4 -7
  88. package/src/ui/screens/SessionManagementScreen.tsx +7 -15
  89. package/src/ui/screens/SignInScreen.tsx +729 -52
  90. package/src/ui/screens/SignUpScreen.tsx +7 -5
  91. package/src/ui/styles/theme.ts +1 -1
  92. package/CHANGELOG.md +0 -97
  93. package/UI_COMPONENTS.md +0 -462
package/src/core/index.ts CHANGED
@@ -37,7 +37,12 @@ import {
37
37
  FileUploadResponse,
38
38
  FileListResponse,
39
39
  FileUpdateRequest,
40
- FileDeleteResponse
40
+ FileDeleteResponse,
41
+ // Device session interfaces
42
+ DeviceSession,
43
+ DeviceSessionsResponse,
44
+ DeviceSessionLogoutResponse,
45
+ UpdateDeviceNameResponse
41
46
  } from '../models/interfaces';
42
47
 
43
48
  // Import secure session types
@@ -154,8 +159,10 @@ export class OxyServices {
154
159
 
155
160
  try {
156
161
  const decoded = jwtDecode<JwtPayload>(this.accessToken);
157
- return decoded.userId;
158
- } catch {
162
+
163
+ // Check for both userId (preferred) and id (fallback) for compatibility
164
+ return decoded.userId || (decoded as any).id || null;
165
+ } catch (error) {
159
166
  return null;
160
167
  }
161
168
  }
@@ -340,6 +347,69 @@ export class OxyServices {
340
347
  }
341
348
  }
342
349
 
350
+ /**
351
+ * Get device sessions for a specific session ID
352
+ * @param sessionId - The session ID to get device sessions for
353
+ * @param deviceId - Optional device ID filter
354
+ * @returns Array of device sessions
355
+ */
356
+ async getDeviceSessions(sessionId: string, deviceId?: string): Promise<DeviceSession[]> {
357
+ try {
358
+ const params = deviceId ? { deviceId } : {};
359
+ const res = await this.client.get(`/secure-session/device/sessions/${sessionId}`, { params });
360
+
361
+ // Map backend response to frontend interface
362
+ return (res.data.sessions || []).map((session: any) => ({
363
+ sessionId: session.sessionId,
364
+ deviceId: res.data.deviceId || '',
365
+ deviceName: session.deviceInfo?.deviceName || 'Unknown Device',
366
+ isActive: true, // All returned sessions are active
367
+ lastActive: session.lastActive,
368
+ expiresAt: session.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
369
+ isCurrent: session.sessionId === sessionId,
370
+ user: session.user,
371
+ createdAt: session.createdAt
372
+ }));
373
+ } catch (error) {
374
+ throw this.handleError(error);
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Logout all device sessions for a specific device
380
+ * @param sessionId - The session ID
381
+ * @param deviceId - Optional device ID (uses current session's device if not provided)
382
+ * @param excludeCurrent - Whether to exclude the current session from logout
383
+ * @returns Logout response
384
+ */
385
+ async logoutAllDeviceSessions(sessionId: string, deviceId?: string, excludeCurrent?: boolean): Promise<DeviceSessionLogoutResponse> {
386
+ try {
387
+ const data: any = {};
388
+ if (deviceId) data.deviceId = deviceId;
389
+ if (excludeCurrent !== undefined) data.excludeCurrent = excludeCurrent;
390
+
391
+ const res = await this.client.post(`/secure-session/device/logout-all/${sessionId}`, data);
392
+ return res.data;
393
+ } catch (error) {
394
+ throw this.handleError(error);
395
+ }
396
+ }
397
+
398
+ /**
399
+ * Update device name for a session
400
+ * @param sessionId - The session ID
401
+ * @param deviceName - The new device name
402
+ * @returns Update response
403
+ */
404
+ async updateDeviceName(sessionId: string, deviceName: string): Promise<UpdateDeviceNameResponse> {
405
+ try {
406
+ const res = await this.client.put(`/secure-session/device/name/${sessionId}`, { deviceName });
407
+ return res.data;
408
+ } catch (error) {
409
+ throw this.handleError(error);
410
+ }
411
+ }
412
+
343
413
  /* Profile Methods */
344
414
 
345
415
  /**
@@ -1162,34 +1232,22 @@ export class OxyServices {
1162
1232
  }
1163
1233
 
1164
1234
  /**
1165
- * Get all active sessions for a specific device
1166
- * @param sessionId - Current session ID
1167
- * @param deviceId - Optional device ID (uses current session's device if not provided)
1168
- * @returns Array of device sessions
1235
+ * Validate session using x-session-id header
1236
+ * @param sessionId - The session ID to validate (sent as header)
1237
+ * @param deviceFingerprint - Optional device fingerprint for enhanced security
1238
+ * @returns Session validation status with user data
1169
1239
  */
1170
- async getDeviceSessions(sessionId: string, deviceId?: string): Promise<any[]> {
1240
+ async validateSessionFromHeader(sessionId: string, deviceFingerprint?: string): Promise<{ valid: boolean; expiresAt: string; lastActivity: string; user: User; sessionId?: string }> {
1171
1241
  try {
1172
- const params = deviceId ? `?deviceId=${deviceId}` : '';
1173
- const res = await this.client.get(`/secure-session/device/sessions/${sessionId}${params}`);
1174
- return res.data.sessions;
1175
- } catch (error) {
1176
- throw this.handleError(error);
1177
- }
1178
- }
1242
+ const headers: any = {
1243
+ 'x-session-id': sessionId
1244
+ };
1245
+
1246
+ if (deviceFingerprint) {
1247
+ headers['x-device-fingerprint'] = deviceFingerprint;
1248
+ }
1179
1249
 
1180
- /**
1181
- * Logout all sessions on a specific device
1182
- * @param sessionId - Current session ID
1183
- * @param deviceId - Optional device ID (uses current session's device if not provided)
1184
- * @param excludeCurrent - Whether to exclude the current session from logout
1185
- * @returns Success status
1186
- */
1187
- async logoutAllDeviceSessions(sessionId: string, deviceId?: string, excludeCurrent: boolean = true): Promise<{ message: string; sessionsTerminated: number }> {
1188
- try {
1189
- const res = await this.client.post(`/secure-session/device/logout-all/${sessionId}`, {
1190
- deviceId,
1191
- excludeCurrent
1192
- });
1250
+ const res = await this.client.get('/secure-session/validate-header', { headers });
1193
1251
  return res.data;
1194
1252
  } catch (error) {
1195
1253
  throw this.handleError(error);
@@ -1197,16 +1255,25 @@ export class OxyServices {
1197
1255
  }
1198
1256
 
1199
1257
  /**
1200
- * Update device name for all sessions on a device
1201
- * @param sessionId - Current session ID
1202
- * @param deviceName - New device name
1203
- * @returns Success status
1258
+ * Validate session using automatic header detection
1259
+ * The validateSession endpoint will automatically read from x-session-id header
1260
+ * @param sessionId - The session ID to validate (sent as header)
1261
+ * @param deviceFingerprint - Optional device fingerprint for enhanced security
1262
+ * @returns Session validation status with user data
1204
1263
  */
1205
- async updateDeviceName(sessionId: string, deviceName: string): Promise<{ message: string; deviceName: string }> {
1264
+ async validateSessionAuto(sessionId: string, deviceFingerprint?: string): Promise<{ valid: boolean; expiresAt: string; lastActivity: string; user: User; source?: string }> {
1206
1265
  try {
1207
- const res = await this.client.put(`/secure-session/device/name/${sessionId}`, {
1208
- deviceName
1209
- });
1266
+ const headers: any = {
1267
+ 'x-session-id': sessionId
1268
+ };
1269
+
1270
+ if (deviceFingerprint) {
1271
+ headers['x-device-fingerprint'] = deviceFingerprint;
1272
+ }
1273
+
1274
+ // Call the regular validateSession endpoint which now auto-reads from headers
1275
+ // Use 'auto' as placeholder since the controller reads from header
1276
+ const res = await this.client.get('/secure-session/validate/auto', { headers });
1210
1277
  return res.data;
1211
1278
  } catch (error) {
1212
1279
  throw this.handleError(error);
@@ -1275,6 +1342,7 @@ export class OxyServices {
1275
1342
 
1276
1343
  // Get user ID from token
1277
1344
  const userId = tempOxyServices.getCurrentUserId();
1345
+
1278
1346
  if (!userId) {
1279
1347
  const error = {
1280
1348
  message: 'Invalid token payload',
@@ -0,0 +1 @@
1
+ export * from 'sonner-native';
@@ -0,0 +1 @@
1
+ export * from 'sonner';
@@ -186,4 +186,33 @@ export interface FileDeleteResponse {
186
186
  success: boolean;
187
187
  message: string;
188
188
  fileId: string;
189
+ }
190
+
191
+ // Device Session interfaces
192
+ export interface DeviceSession {
193
+ sessionId: string;
194
+ deviceId: string;
195
+ deviceName: string;
196
+ isActive: boolean;
197
+ lastActive: string;
198
+ expiresAt: string;
199
+ isCurrent: boolean;
200
+ user?: User;
201
+ createdAt?: string;
202
+ }
203
+
204
+ export interface DeviceSessionsResponse {
205
+ deviceId: string;
206
+ sessions: DeviceSession[];
207
+ }
208
+
209
+ export interface DeviceSessionLogoutResponse {
210
+ message: string;
211
+ deviceId: string;
212
+ sessionsTerminated: number;
213
+ }
214
+
215
+ export interface UpdateDeviceNameResponse {
216
+ message: string;
217
+ deviceName: string;
189
218
  }
@@ -19,6 +19,7 @@ import Animated, {
19
19
  } from 'react-native-reanimated';
20
20
  import { useOxy } from '../context/OxyContext';
21
21
  import { fontFamilies } from '../styles/fonts';
22
+ import { toast } from '../../lib/sonner';
22
23
 
23
24
  export interface FollowButtonProps {
24
25
  /**
@@ -64,10 +65,23 @@ export interface FollowButtonProps {
64
65
  * @default true
65
66
  */
66
67
  showLoadingState?: boolean;
68
+
69
+ /**
70
+ * Whether to prevent default action and stop event propagation
71
+ * Useful when the button is inside links or other pressable containers
72
+ * @default true
73
+ */
74
+ preventParentActions?: boolean;
75
+
76
+ /**
77
+ * Custom onPress handler - if provided, will override default follow/unfollow behavior
78
+ * Event object is passed to allow for preventDefault/stopPropagation
79
+ */
80
+ onPress?: (event: any) => void;
67
81
  }
68
82
 
69
83
  /**
70
- * An animated follow button with interactive state changes
84
+ * An animated follow button with interactive state changes and preventDefault support
71
85
  *
72
86
  * @example
73
87
  * ```tsx
@@ -82,6 +96,26 @@ export interface FollowButtonProps {
82
96
  * style={{ borderRadius: 12 }}
83
97
  * onFollowChange={(isFollowing) => console.log(`User is now ${isFollowing ? 'followed' : 'unfollowed'}`)}
84
98
  * />
99
+ *
100
+ * // Inside a pressable container (prevents parent actions)
101
+ * <TouchableOpacity onPress={() => navigateToProfile()}>
102
+ * <View>
103
+ * <Text>User Profile</Text>
104
+ * <FollowButton
105
+ * userId="123"
106
+ * preventParentActions={true} // Default: true
107
+ * />
108
+ * </View>
109
+ * </TouchableOpacity>
110
+ *
111
+ * // Custom onPress handler
112
+ * <FollowButton
113
+ * userId="123"
114
+ * onPress={(event) => {
115
+ * event.preventDefault(); // Custom preventDefault
116
+ * // Custom logic here
117
+ * }}
118
+ * />
85
119
  * ```
86
120
  */
87
121
  const FollowButton: React.FC<FollowButtonProps> = ({
@@ -93,6 +127,8 @@ const FollowButton: React.FC<FollowButtonProps> = ({
93
127
  textStyle,
94
128
  disabled = false,
95
129
  showLoadingState = true,
130
+ preventParentActions = true,
131
+ onPress,
96
132
  }) => {
97
133
  const { oxyServices, isAuthenticated } = useOxy();
98
134
  const [isFollowing, setIsFollowing] = useState(initiallyFollowing);
@@ -110,8 +146,32 @@ const FollowButton: React.FC<FollowButtonProps> = ({
110
146
  });
111
147
  }, [isFollowing, animationProgress]);
112
148
 
113
- // The button press handler
114
- const handlePress = async () => {
149
+ // The button press handler with preventDefault support
150
+ const handlePress = async (event?: any) => {
151
+ // Prevent parent actions if enabled (e.g., if inside a link or pressable container)
152
+ if (preventParentActions && event) {
153
+ // For React Native Web compatibility
154
+ if (Platform.OS === 'web' && event.preventDefault) {
155
+ event.preventDefault();
156
+ }
157
+
158
+ // Stop event propagation to prevent parent TouchableOpacity/Pressable actions
159
+ if (event.stopPropagation) {
160
+ event.stopPropagation();
161
+ }
162
+
163
+ // For React Native, prevent gesture bubbling
164
+ if (event.nativeEvent && event.nativeEvent.stopPropagation) {
165
+ event.nativeEvent.stopPropagation();
166
+ }
167
+ }
168
+
169
+ // If custom onPress is provided, use it instead of default behavior
170
+ if (onPress) {
171
+ onPress(event);
172
+ return;
173
+ }
174
+
115
175
  if (disabled || isLoading || !isAuthenticated) return;
116
176
 
117
177
  // Touch feedback animation
@@ -143,8 +203,12 @@ const FollowButton: React.FC<FollowButtonProps> = ({
143
203
  if (onFollowChange) {
144
204
  onFollowChange(newFollowingState);
145
205
  }
206
+
207
+ // Show success toast
208
+ toast.success(newFollowingState ? 'Following user!' : 'Unfollowed user');
146
209
  } catch (error) {
147
210
  console.error('Follow action failed:', error);
211
+ toast.error('Failed to update follow status. Please try again.');
148
212
  } finally {
149
213
  setIsLoading(false);
150
214
  }
@@ -11,6 +11,7 @@ import AccountCenterScreen from '../screens/AccountCenterScreen';
11
11
  import { OxyContextProvider, useOxy } from '../context/OxyContext';
12
12
  import OxyRouter from '../navigation/OxyRouter';
13
13
  import { FontLoader, setupFonts } from './FontLoader';
14
+ import { Toaster } from '../../lib/sonner';
14
15
 
15
16
  // Import bottom sheet components directly - no longer a peer dependency
16
17
  import { BottomSheetModal, BottomSheetBackdrop, BottomSheetBackdropProps, BottomSheetModalProvider, BottomSheetView } from './bottomSheet';
@@ -71,6 +72,10 @@ const OxyProvider: React.FC<OxyProviderProps> = (props) => {
71
72
  {children}
72
73
  </SafeAreaProvider>
73
74
  </BottomSheetModalProvider>
75
+ {/* Move Toaster outside BottomSheetModalProvider to ensure it appears above the modal backdrop */}
76
+ <View style={styles.toasterContainer}>
77
+ <Toaster position="top-center" swipeToDismissDirection="left" offset={15} />
78
+ </View>
74
79
  </GestureHandlerRootView>
75
80
  </FontLoader>
76
81
  </OxyContextProvider>
@@ -532,6 +537,16 @@ const styles = StyleSheet.create({
532
537
  }
533
538
  })
534
539
  },
540
+ toasterContainer: {
541
+ position: 'absolute',
542
+ top: 0,
543
+ left: 0,
544
+ right: 0,
545
+ bottom: 0,
546
+ zIndex: 9999,
547
+ elevation: 9999, // For Android
548
+ pointerEvents: 'box-none', // Allow touches to pass through to underlying components
549
+ },
535
550
  });
536
551
 
537
552
  export default OxyProvider;
@@ -14,6 +14,7 @@ import { BaseScreenProps } from '../navigation/types';
14
14
  import { useOxy } from '../context/OxyContext';
15
15
  import { fontFamilies } from '../styles/fonts';
16
16
  import { packageInfo } from '../../constants/version';
17
+ import { toast } from '../../lib/sonner';
17
18
 
18
19
  const AccountCenterScreen: React.FC<BaseScreenProps> = ({
19
20
  onClose,
@@ -38,7 +39,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
38
39
  }
39
40
  } catch (error) {
40
41
  console.error('Logout failed:', error);
41
- Alert.alert('Logout Failed', 'There was a problem signing you out. Please try again.');
42
+ toast.error('There was a problem signing you out. Please try again.');
42
43
  }
43
44
  };
44
45
 
@@ -138,7 +139,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
138
139
  {Platform.OS !== 'web' && (
139
140
  <TouchableOpacity
140
141
  style={[styles.actionButton, { borderColor }]}
141
- onPress={() => Alert.alert('Notifications', 'Notifications feature coming soon!')}
142
+ onPress={() => toast.info('Notifications feature coming soon!')}
142
143
  >
143
144
  <Text style={[styles.actionButtonText, { color: textColor }]}>Notifications</Text>
144
145
  </TouchableOpacity>
@@ -146,7 +147,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
146
147
 
147
148
  <TouchableOpacity
148
149
  style={[styles.actionButton, { borderColor }]}
149
- onPress={() => Alert.alert('Help', 'Help & Support feature coming soon!')}
150
+ onPress={() => toast.info('Help & Support feature coming soon!')}
150
151
  >
151
152
  <Text style={[styles.actionButtonText, { color: textColor }]}>Help & Support</Text>
152
153
  </TouchableOpacity>
@@ -16,6 +16,7 @@ import { useOxy } from '../context/OxyContext';
16
16
  import OxyLogo from '../components/OxyLogo';
17
17
  import Avatar from '../components/Avatar';
18
18
  import { fontFamilies } from '../styles/fonts';
19
+ import { toast } from '../../lib/sonner';
19
20
 
20
21
  const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
21
22
  onClose,
@@ -59,7 +60,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
59
60
  }
60
61
  } catch (error) {
61
62
  console.error('Logout failed:', error);
62
- Alert.alert('Logout Failed', 'There was a problem signing you out. Please try again.');
63
+ toast.error('There was a problem signing you out. Please try again.');
63
64
  }
64
65
  };
65
66
 
@@ -83,7 +84,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
83
84
  };
84
85
 
85
86
  const handleAddAccount = () => {
86
- Alert.alert('Add Account', 'Add another account feature coming soon!');
87
+ toast.info('Add another account feature coming soon!');
87
88
  };
88
89
 
89
90
  const handleSignOutAll = () => {
@@ -168,7 +169,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
168
169
 
169
170
  <TouchableOpacity
170
171
  style={[styles.manageAccountButton, { borderColor }]}
171
- onPress={() => Alert.alert('Account', 'Manage your Oxy Account feature coming soon!')}
172
+ onPress={() => toast.info('Manage your Oxy Account feature coming soon!')}
172
173
  >
173
174
  <Text style={[styles.manageAccountText, { color: textColor }]}>
174
175
  Manage your Oxy Account
@@ -195,7 +196,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
195
196
  <TouchableOpacity
196
197
  key={account.id}
197
198
  style={[styles.accountItem, { borderColor }]}
198
- onPress={() => Alert.alert('Switch Account', `Switch to ${account.username}?`)}
199
+ onPress={() => toast.info(`Switch to ${account.username}?`)}
199
200
  >
200
201
  <View style={styles.accountItemLeft}>
201
202
  {account.avatar.url ? (
@@ -260,42 +261,42 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
260
261
  <Text style={{ fontSize: 18 }}>🕒</Text>,
261
262
  'History',
262
263
  'Saving',
263
- () => Alert.alert('History', 'View your history feature coming soon!')
264
+ () => toast.info('View your history feature coming soon!')
264
265
  )}
265
266
 
266
267
  {renderFeatureItem(
267
268
  <Text style={{ fontSize: 18 }}>⏱️</Text>,
268
269
  'Delete last 15 minutes',
269
270
  null,
270
- () => Alert.alert('Delete History', 'Delete recent history feature coming soon!')
271
+ () => toast.info('Delete recent history feature coming soon!')
271
272
  )}
272
273
 
273
274
  {renderFeatureItem(
274
275
  <Text style={{ fontSize: 18 }}>📋</Text>,
275
276
  'Saves & Collections',
276
277
  null,
277
- () => Alert.alert('Saves', 'Saved items feature coming soon!')
278
+ () => toast.info('Saved items feature coming soon!')
278
279
  )}
279
280
 
280
281
  {renderFeatureItem(
281
282
  <Text style={{ fontSize: 18 }}>🔍</Text>,
282
283
  'Search personalization',
283
284
  null,
284
- () => Alert.alert('Personalization', 'Search personalization feature coming soon!')
285
+ () => toast.info('Search personalization feature coming soon!')
285
286
  )}
286
287
 
287
288
  {renderFeatureItem(
288
289
  <Text style={{ fontSize: 18 }}>🛡️</Text>,
289
290
  'SafeSearch',
290
291
  features.safeSearch ? 'On' : 'Off',
291
- () => Alert.alert('SafeSearch', 'SafeSearch settings feature coming soon!')
292
+ () => toast.info('SafeSearch settings feature coming soon!')
292
293
  )}
293
294
 
294
295
  {renderFeatureItem(
295
296
  <Text style={{ fontSize: 18 }}>🌐</Text>,
296
297
  'Language',
297
298
  features.language,
298
- () => Alert.alert('Language', 'Language settings feature coming soon!')
299
+ () => toast.info('Language settings feature coming soon!')
299
300
  )}
300
301
  </View>
301
302
 
@@ -303,7 +304,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
303
304
  <View style={styles.footerButtonsRow}>
304
305
  <TouchableOpacity
305
306
  style={styles.footerButton}
306
- onPress={() => Alert.alert('Settings', 'More settings feature coming soon!')}
307
+ onPress={() => toast.info('More settings feature coming soon!')}
307
308
  >
308
309
  <Text style={[styles.footerButtonText, { color: textColor }]}>
309
310
  More settings
@@ -312,7 +313,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
312
313
 
313
314
  <TouchableOpacity
314
315
  style={styles.footerButton}
315
- onPress={() => Alert.alert('Help', 'Help & support feature coming soon!')}
316
+ onPress={() => toast.info('Help & support feature coming soon!')}
316
317
  >
317
318
  <Text style={[styles.footerButtonText, { color: textColor }]}>
318
319
  Help
@@ -321,11 +322,11 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
321
322
  </View>
322
323
 
323
324
  <View style={styles.footerLinksRow}>
324
- <TouchableOpacity onPress={() => Alert.alert('Privacy', 'Privacy Policy feature coming soon!')}>
325
+ <TouchableOpacity onPress={() => toast.info('Privacy Policy feature coming soon!')}>
325
326
  <Text style={[styles.footerLink, { color: iconColor }]}>Privacy Policy</Text>
326
327
  </TouchableOpacity>
327
328
  <Text style={[{ color: iconColor, marginHorizontal: 5 }]}>•</Text>
328
- <TouchableOpacity onPress={() => Alert.alert('Terms', 'Terms of Service feature coming soon!')}>
329
+ <TouchableOpacity onPress={() => toast.info('Terms of Service feature coming soon!')}>
329
330
  <Text style={[styles.footerLink, { color: iconColor }]}>Terms of Service</Text>
330
331
  </TouchableOpacity>
331
332
  </View>
@@ -14,6 +14,7 @@ import {
14
14
  import { BaseScreenProps } from '../navigation/types';
15
15
  import { useOxy } from '../context/OxyContext';
16
16
  import Avatar from '../components/Avatar';
17
+ import { toast } from '../../lib/sonner';
17
18
 
18
19
  interface AccountSettingsScreenProps extends BaseScreenProps {
19
20
  activeTab?: 'profile' | 'password' | 'notifications';
@@ -104,9 +105,9 @@ const AccountSettingsScreen: React.FC<AccountSettingsScreenProps> = ({
104
105
 
105
106
  // Call API to update user
106
107
  await oxyServices.updateUser(user!.id, updates);
107
- setSuccessMessage('Profile updated successfully');
108
+ toast.success('Profile updated successfully');
108
109
  } catch (error: any) {
109
- setErrorMessage(error.message || 'Failed to update profile');
110
+ toast.error(error.message || 'Failed to update profile');
110
111
  } finally {
111
112
  setIsLoading(false);
112
113
  }
@@ -115,17 +116,17 @@ const AccountSettingsScreen: React.FC<AccountSettingsScreenProps> = ({
115
116
  const handleChangePassword = async () => {
116
117
  // Validate inputs
117
118
  if (!currentPassword || !newPassword || !confirmPassword) {
118
- setErrorMessage('Please fill in all password fields');
119
+ toast.error('Please fill in all password fields');
119
120
  return;
120
121
  }
121
122
 
122
123
  if (newPassword !== confirmPassword) {
123
- setErrorMessage('New passwords do not match');
124
+ toast.error('New passwords do not match');
124
125
  return;
125
126
  }
126
127
 
127
128
  if (newPassword.length < 8) {
128
- setErrorMessage('Password must be at least 8 characters long');
129
+ toast.error('Password must be at least 8 characters long');
129
130
  return;
130
131
  }
131
132
 
@@ -144,9 +145,9 @@ const AccountSettingsScreen: React.FC<AccountSettingsScreenProps> = ({
144
145
  setCurrentPassword('');
145
146
  setNewPassword('');
146
147
  setConfirmPassword('');
147
- setSuccessMessage('Password updated successfully');
148
+ toast.success('Password updated successfully');
148
149
  } catch (error: any) {
149
- setErrorMessage(error.message || 'Failed to update password');
150
+ toast.error(error.message || 'Failed to update password');
150
151
  } finally {
151
152
  setIsLoading(false);
152
153
  }
@@ -165,9 +166,9 @@ const AccountSettingsScreen: React.FC<AccountSettingsScreenProps> = ({
165
166
  push: pushNotifications,
166
167
  },
167
168
  });
168
- setSuccessMessage('Notification preferences updated successfully');
169
+ toast.success('Notification preferences updated successfully');
169
170
  } catch (error: any) {
170
- setErrorMessage(error.message || 'Failed to update notification preferences');
171
+ toast.error(error.message || 'Failed to update notification preferences');
171
172
  } finally {
172
173
  setIsLoading(false);
173
174
  }