@oxyhq/services 5.16.4 → 5.16.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 (82) hide show
  1. package/lib/commonjs/core/OxyServices.base.js +3 -1
  2. package/lib/commonjs/core/OxyServices.base.js.map +1 -1
  3. package/lib/commonjs/core/mixins/OxyServices.assets.js +20 -330
  4. package/lib/commonjs/core/mixins/OxyServices.assets.js.map +1 -1
  5. package/lib/commonjs/index.js +156 -0
  6. package/lib/commonjs/index.js.map +1 -1
  7. package/lib/commonjs/ui/context/OxyContext.js +45 -7
  8. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  9. package/lib/commonjs/ui/hooks/mutations/index.js +60 -20
  10. package/lib/commonjs/ui/hooks/mutations/index.js.map +1 -1
  11. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +230 -1
  12. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  13. package/lib/commonjs/ui/hooks/queries/index.js +96 -30
  14. package/lib/commonjs/ui/hooks/queries/index.js.map +1 -1
  15. package/lib/commonjs/ui/hooks/queries/queryKeys.js +5 -0
  16. package/lib/commonjs/ui/hooks/queries/queryKeys.js.map +1 -1
  17. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +75 -1
  18. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
  19. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js +50 -2
  20. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js.map +1 -1
  21. package/lib/commonjs/ui/hooks/useAssets.js +8 -29
  22. package/lib/commonjs/ui/hooks/useAssets.js.map +1 -1
  23. package/lib/commonjs/ui/screens/FileManagementScreen.js +14 -10
  24. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  25. package/lib/module/core/OxyServices.base.js +3 -1
  26. package/lib/module/core/OxyServices.base.js.map +1 -1
  27. package/lib/module/core/mixins/OxyServices.assets.js +20 -331
  28. package/lib/module/core/mixins/OxyServices.assets.js.map +1 -1
  29. package/lib/module/index.js +17 -1
  30. package/lib/module/index.js.map +1 -1
  31. package/lib/module/ui/context/OxyContext.js +45 -7
  32. package/lib/module/ui/context/OxyContext.js.map +1 -1
  33. package/lib/module/ui/hooks/mutations/index.js +12 -3
  34. package/lib/module/ui/hooks/mutations/index.js.map +1 -1
  35. package/lib/module/ui/hooks/mutations/useAccountMutations.js +227 -0
  36. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  37. package/lib/module/ui/hooks/queries/index.js +15 -4
  38. package/lib/module/ui/hooks/queries/index.js.map +1 -1
  39. package/lib/module/ui/hooks/queries/queryKeys.js +5 -0
  40. package/lib/module/ui/hooks/queries/queryKeys.js.map +1 -1
  41. package/lib/module/ui/hooks/queries/useAccountQueries.js +73 -0
  42. package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
  43. package/lib/module/ui/hooks/queries/useServicesQueries.js +50 -2
  44. package/lib/module/ui/hooks/queries/useServicesQueries.js.map +1 -1
  45. package/lib/module/ui/hooks/useAssets.js +8 -29
  46. package/lib/module/ui/hooks/useAssets.js.map +1 -1
  47. package/lib/module/ui/screens/FileManagementScreen.js +12 -10
  48. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  49. package/lib/typescript/core/OxyServices.base.d.ts.map +1 -1
  50. package/lib/typescript/core/mixins/OxyServices.assets.d.ts +1 -70
  51. package/lib/typescript/core/mixins/OxyServices.assets.d.ts.map +1 -1
  52. package/lib/typescript/core/mixins/index.d.ts +4 -14
  53. package/lib/typescript/core/mixins/index.d.ts.map +1 -1
  54. package/lib/typescript/index.d.ts +2 -0
  55. package/lib/typescript/index.d.ts.map +1 -1
  56. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  57. package/lib/typescript/ui/hooks/mutations/index.d.ts +8 -2
  58. package/lib/typescript/ui/hooks/mutations/index.d.ts.map +1 -1
  59. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts +19 -0
  60. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  61. package/lib/typescript/ui/hooks/queries/index.d.ts +9 -3
  62. package/lib/typescript/ui/hooks/queries/index.d.ts.map +1 -1
  63. package/lib/typescript/ui/hooks/queries/queryKeys.d.ts +4 -0
  64. package/lib/typescript/ui/hooks/queries/queryKeys.d.ts.map +1 -1
  65. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts +6 -0
  66. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  67. package/lib/typescript/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
  68. package/lib/typescript/ui/hooks/useAssets.d.ts.map +1 -1
  69. package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
  70. package/package.json +1 -1
  71. package/src/core/OxyServices.base.ts +5 -1
  72. package/src/core/mixins/OxyServices.assets.ts +21 -338
  73. package/src/index.ts +49 -2
  74. package/src/ui/context/OxyContext.tsx +49 -7
  75. package/src/ui/hooks/mutations/index.ts +24 -3
  76. package/src/ui/hooks/mutations/useAccountMutations.ts +205 -0
  77. package/src/ui/hooks/queries/index.ts +29 -4
  78. package/src/ui/hooks/queries/queryKeys.ts +6 -0
  79. package/src/ui/hooks/queries/useAccountQueries.ts +69 -0
  80. package/src/ui/hooks/queries/useServicesQueries.ts +49 -2
  81. package/src/ui/hooks/useAssets.ts +8 -28
  82. package/src/ui/screens/FileManagementScreen.tsx +10 -11
@@ -1,4 +1,25 @@
1
- // Export all mutation hooks
2
- export * from './useAccountMutations';
3
- export * from './useServicesMutations';
1
+ /**
2
+ * Mutation Hooks
3
+ *
4
+ * TanStack Query mutation hooks for updating Oxy services data.
5
+ * All mutations handle authentication, error handling, and query invalidation.
6
+ */
7
+
8
+ // Account mutation hooks
9
+ export {
10
+ useUpdateProfile,
11
+ useUploadAvatar,
12
+ useUpdateAccountSettings,
13
+ useUpdatePrivacySettings,
14
+ useUploadFile,
15
+ } from './useAccountMutations';
16
+
17
+ // Service mutation hooks (sessions, devices)
18
+ export {
19
+ useSwitchSession,
20
+ useLogoutSession,
21
+ useLogoutAll,
22
+ useUpdateDeviceName,
23
+ useRemoveDevice,
24
+ } from './useServicesMutations';
4
25
 
@@ -275,3 +275,208 @@ export const useUpdateAccountSettings = () => {
275
275
  });
276
276
  };
277
277
 
278
+ /**
279
+ * Update privacy settings with optimistic updates and authentication handling
280
+ */
281
+ export const useUpdatePrivacySettings = () => {
282
+ const { oxyServices, activeSessionId, user, syncIdentity } = useOxy();
283
+ const queryClient = useQueryClient();
284
+
285
+ return useMutation({
286
+ mutationFn: async ({ settings, userId }: { settings: Record<string, any>; userId?: string }) => {
287
+ const targetUserId = userId || user?.id;
288
+ if (!targetUserId) {
289
+ throw new Error('User ID is required');
290
+ }
291
+
292
+ // Ensure we have a valid token before making the request
293
+ if (!oxyServices.hasValidToken() && activeSessionId) {
294
+ try {
295
+ // Try to get token for the session
296
+ await oxyServices.getTokenBySession(activeSessionId);
297
+ } catch (tokenError) {
298
+ // If getting token fails, might be an offline session - try syncing
299
+ const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
300
+ if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
301
+ try {
302
+ await syncIdentity();
303
+ // Retry getting token after sync
304
+ await oxyServices.getTokenBySession(activeSessionId);
305
+ } catch (syncError) {
306
+ throw new Error('Session needs to be synced. Please try again.');
307
+ }
308
+ } else {
309
+ throw tokenError;
310
+ }
311
+ }
312
+ }
313
+
314
+ try {
315
+ return await oxyServices.updatePrivacySettings(settings, targetUserId);
316
+ } catch (error: any) {
317
+ const errorMessage = error?.message || '';
318
+ const status = error?.status || error?.response?.status;
319
+
320
+ // Handle authentication errors
321
+ if (status === 401 || errorMessage.includes('Authentication required') || errorMessage.includes('Invalid or missing authorization header')) {
322
+ // Try to sync session and get token
323
+ if (activeSessionId) {
324
+ try {
325
+ await syncIdentity();
326
+ await oxyServices.getTokenBySession(activeSessionId);
327
+ // Retry the update after getting token
328
+ return await oxyServices.updatePrivacySettings(settings, targetUserId);
329
+ } catch (retryError) {
330
+ throw new Error('Authentication failed. Please sign in again.');
331
+ }
332
+ } else {
333
+ throw new Error('No active session. Please sign in.');
334
+ }
335
+ }
336
+
337
+ // TanStack Query will automatically retry on network errors
338
+ throw error;
339
+ }
340
+ },
341
+ // Optimistic update
342
+ onMutate: async ({ settings, userId }) => {
343
+ const targetUserId = userId || user?.id;
344
+ if (!targetUserId) return;
345
+
346
+ // Cancel outgoing refetches
347
+ await queryClient.cancelQueries({ queryKey: queryKeys.privacy.settings(targetUserId) });
348
+ await queryClient.cancelQueries({ queryKey: queryKeys.accounts.current() });
349
+
350
+ // Snapshot previous values
351
+ const previousPrivacySettings = queryClient.getQueryData(queryKeys.privacy.settings(targetUserId));
352
+ const previousUser = queryClient.getQueryData<User>(queryKeys.accounts.current());
353
+
354
+ // Optimistically update privacy settings
355
+ if (previousPrivacySettings) {
356
+ queryClient.setQueryData(queryKeys.privacy.settings(targetUserId), {
357
+ ...previousPrivacySettings,
358
+ ...settings,
359
+ });
360
+ }
361
+
362
+ // Also update user query if available
363
+ if (previousUser) {
364
+ queryClient.setQueryData<User>(queryKeys.accounts.current(), {
365
+ ...previousUser,
366
+ privacySettings: {
367
+ ...previousUser.privacySettings,
368
+ ...settings,
369
+ },
370
+ });
371
+ }
372
+
373
+ return { previousPrivacySettings, previousUser };
374
+ },
375
+ // On error, rollback
376
+ onError: (error, { userId }, context) => {
377
+ const targetUserId = userId || user?.id;
378
+ if (context?.previousPrivacySettings && targetUserId) {
379
+ queryClient.setQueryData(queryKeys.privacy.settings(targetUserId), context.previousPrivacySettings);
380
+ }
381
+ if (context?.previousUser) {
382
+ queryClient.setQueryData(queryKeys.accounts.current(), context.previousUser);
383
+ }
384
+ toast.error(error instanceof Error ? error.message : 'Failed to update privacy settings');
385
+ },
386
+ // On success, invalidate and refetch
387
+ onSuccess: (data, { userId }) => {
388
+ const targetUserId = userId || user?.id;
389
+ if (targetUserId) {
390
+ queryClient.setQueryData(queryKeys.privacy.settings(targetUserId), data);
391
+ }
392
+ // Also update account query if it contains privacy settings
393
+ const currentUser = queryClient.getQueryData<User>(queryKeys.accounts.current());
394
+ if (currentUser) {
395
+ queryClient.setQueryData<User>(queryKeys.accounts.current(), {
396
+ ...currentUser,
397
+ privacySettings: data,
398
+ });
399
+ }
400
+ invalidateAccountQueries(queryClient);
401
+ },
402
+ // Always refetch after error or success
403
+ onSettled: (data, error, { userId }) => {
404
+ const targetUserId = userId || user?.id;
405
+ if (targetUserId) {
406
+ queryClient.invalidateQueries({ queryKey: queryKeys.privacy.settings(targetUserId) });
407
+ }
408
+ queryClient.invalidateQueries({ queryKey: queryKeys.accounts.current() });
409
+ },
410
+ });
411
+ };
412
+
413
+ /**
414
+ * Upload file with authentication handling and progress tracking
415
+ */
416
+ export const useUploadFile = () => {
417
+ const { oxyServices, activeSessionId, syncIdentity } = useOxy();
418
+
419
+ return useMutation({
420
+ mutationFn: async ({
421
+ file,
422
+ visibility,
423
+ metadata,
424
+ onProgress
425
+ }: {
426
+ file: File;
427
+ visibility?: 'private' | 'public' | 'unlisted';
428
+ metadata?: Record<string, any>;
429
+ onProgress?: (progress: number) => void;
430
+ }) => {
431
+ // Ensure we have a valid token before making the request
432
+ if (!oxyServices.hasValidToken() && activeSessionId) {
433
+ try {
434
+ // Try to get token for the session
435
+ await oxyServices.getTokenBySession(activeSessionId);
436
+ } catch (tokenError) {
437
+ // If getting token fails, might be an offline session - try syncing
438
+ const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
439
+ if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
440
+ try {
441
+ await syncIdentity();
442
+ // Retry getting token after sync
443
+ await oxyServices.getTokenBySession(activeSessionId);
444
+ } catch (syncError) {
445
+ throw new Error('Session needs to be synced. Please try again.');
446
+ }
447
+ } else {
448
+ throw tokenError;
449
+ }
450
+ }
451
+ }
452
+
453
+ try {
454
+ return await oxyServices.assetUpload(file as any, visibility, metadata, onProgress);
455
+ } catch (error: any) {
456
+ const errorMessage = error?.message || '';
457
+ const status = error?.status || error?.response?.status;
458
+
459
+ // Handle authentication errors
460
+ if (status === 401 || errorMessage.includes('Authentication required') || errorMessage.includes('Invalid or missing authorization header')) {
461
+ // Try to sync session and get token
462
+ if (activeSessionId) {
463
+ try {
464
+ await syncIdentity();
465
+ await oxyServices.getTokenBySession(activeSessionId);
466
+ // Retry the upload after getting token
467
+ return await oxyServices.assetUpload(file as any, visibility, metadata, onProgress);
468
+ } catch (retryError) {
469
+ throw new Error('Authentication failed. Please sign in again.');
470
+ }
471
+ } else {
472
+ throw new Error('No active session. Please sign in.');
473
+ }
474
+ }
475
+
476
+ // TanStack Query will automatically retry on network errors
477
+ throw error;
478
+ }
479
+ },
480
+ });
481
+ };
482
+
@@ -1,5 +1,30 @@
1
- // Export all query hooks
2
- export * from './useAccountQueries';
3
- export * from './useServicesQueries';
4
- export * from './queryKeys';
1
+ /**
2
+ * Query Hooks
3
+ *
4
+ * TanStack Query hooks for fetching Oxy services data.
5
+ * All hooks follow the same pattern with optional `enabled` parameter.
6
+ */
7
+
8
+ // Account and user query hooks
9
+ export {
10
+ useUserProfile,
11
+ useUserProfiles,
12
+ useCurrentUser,
13
+ useUserById,
14
+ useUserByUsername,
15
+ useUsersBySessions,
16
+ usePrivacySettings,
17
+ } from './useAccountQueries';
18
+
19
+ // Service query hooks (sessions, devices, security)
20
+ export {
21
+ useSessions,
22
+ useSession,
23
+ useDeviceSessions,
24
+ useUserDevices,
25
+ useSecurityInfo,
26
+ } from './useServicesQueries';
27
+
28
+ // Query keys and invalidation helpers (for advanced usage)
29
+ export { queryKeys, invalidateAccountQueries, invalidateUserQueries, invalidateSessionQueries } from './queryKeys';
5
30
 
@@ -48,6 +48,12 @@ export const queryKeys = {
48
48
  details: () => [...queryKeys.devices.all, 'detail'] as const,
49
49
  detail: (deviceId: string) => [...queryKeys.devices.details(), deviceId] as const,
50
50
  },
51
+
52
+ // Privacy settings queries
53
+ privacy: {
54
+ all: ['privacy'] as const,
55
+ settings: (userId?: string) => [...queryKeys.privacy.all, 'settings', userId || 'current'] as const,
56
+ },
51
57
  } as const;
52
58
 
53
59
  /**
@@ -124,3 +124,72 @@ export const useUsersBySessions = (sessionIds: string[], options?: { enabled?: b
124
124
  });
125
125
  };
126
126
 
127
+ /**
128
+ * Get privacy settings for a user
129
+ */
130
+ export const usePrivacySettings = (userId?: string, options?: { enabled?: boolean }) => {
131
+ const { oxyServices, activeSessionId, syncIdentity, user } = useOxy();
132
+ const targetUserId = userId || user?.id;
133
+
134
+ return useQuery({
135
+ queryKey: queryKeys.privacy.settings(userId),
136
+ queryFn: async () => {
137
+ if (!targetUserId) {
138
+ throw new Error('User ID is required');
139
+ }
140
+
141
+ // Ensure we have a valid token before making the request
142
+ if (!oxyServices.hasValidToken() && activeSessionId) {
143
+ try {
144
+ // Try to get token for the session
145
+ await oxyServices.getTokenBySession(activeSessionId);
146
+ } catch (tokenError) {
147
+ // If getting token fails, might be an offline session - try syncing
148
+ const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
149
+ if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
150
+ try {
151
+ await syncIdentity();
152
+ // Retry getting token after sync
153
+ await oxyServices.getTokenBySession(activeSessionId);
154
+ } catch (syncError) {
155
+ throw new Error('Session needs to be synced. Please try again.');
156
+ }
157
+ } else {
158
+ throw tokenError;
159
+ }
160
+ }
161
+ }
162
+
163
+ try {
164
+ return await oxyServices.getPrivacySettings(targetUserId);
165
+ } catch (error: any) {
166
+ const errorMessage = error?.message || '';
167
+ const status = error?.status || error?.response?.status;
168
+
169
+ // Handle authentication errors
170
+ if (status === 401 || errorMessage.includes('Authentication required') || errorMessage.includes('Invalid or missing authorization header')) {
171
+ // Try to sync session and get token
172
+ if (activeSessionId) {
173
+ try {
174
+ await syncIdentity();
175
+ await oxyServices.getTokenBySession(activeSessionId);
176
+ // Retry the request after getting token
177
+ return await oxyServices.getPrivacySettings(targetUserId);
178
+ } catch (retryError) {
179
+ throw new Error('Authentication failed. Please sign in again.');
180
+ }
181
+ } else {
182
+ throw new Error('No active session. Please sign in.');
183
+ }
184
+ }
185
+
186
+ // TanStack Query will automatically retry on network errors
187
+ throw error;
188
+ }
189
+ },
190
+ enabled: (options?.enabled !== false) && !!targetUserId,
191
+ staleTime: 2 * 60 * 1000, // 2 minutes
192
+ gcTime: 10 * 60 * 1000, // 10 minutes
193
+ });
194
+ };
195
+
@@ -89,12 +89,59 @@ export const useDeviceSessions = (options?: { enabled?: boolean }) => {
89
89
  * Get user devices
90
90
  */
91
91
  export const useUserDevices = (options?: { enabled?: boolean }) => {
92
- const { oxyServices, isAuthenticated } = useOxy();
92
+ const { oxyServices, isAuthenticated, activeSessionId, syncIdentity } = useOxy();
93
93
 
94
94
  return useQuery({
95
95
  queryKey: queryKeys.devices.list(),
96
96
  queryFn: async () => {
97
- return await oxyServices.getUserDevices();
97
+ // Ensure we have a valid token before making the request
98
+ if (!oxyServices.hasValidToken() && activeSessionId) {
99
+ try {
100
+ // Try to get token for the session
101
+ await oxyServices.getTokenBySession(activeSessionId);
102
+ } catch (tokenError) {
103
+ // If getting token fails, might be an offline session - try syncing
104
+ const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
105
+ if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
106
+ try {
107
+ await syncIdentity();
108
+ // Retry getting token after sync
109
+ await oxyServices.getTokenBySession(activeSessionId);
110
+ } catch (syncError) {
111
+ throw new Error('Session needs to be synced. Please try again.');
112
+ }
113
+ } else {
114
+ throw tokenError;
115
+ }
116
+ }
117
+ }
118
+
119
+ try {
120
+ return await oxyServices.getUserDevices();
121
+ } catch (error: any) {
122
+ const errorMessage = error?.message || '';
123
+ const status = error?.status || error?.response?.status;
124
+
125
+ // Handle authentication errors
126
+ if (status === 401 || errorMessage.includes('Authentication required') || errorMessage.includes('Invalid or missing authorization header')) {
127
+ // Try to sync session and get token
128
+ if (activeSessionId) {
129
+ try {
130
+ await syncIdentity();
131
+ await oxyServices.getTokenBySession(activeSessionId);
132
+ // Retry the request after getting token
133
+ return await oxyServices.getUserDevices();
134
+ } catch (retryError) {
135
+ throw new Error('Authentication failed. Please sign in again.');
136
+ }
137
+ } else {
138
+ throw new Error('No active session. Please sign in.');
139
+ }
140
+ }
141
+
142
+ // TanStack Query will automatically retry on network errors
143
+ throw error;
144
+ }
98
145
  },
99
146
  enabled: (options?.enabled !== false) && isAuthenticated,
100
147
  staleTime: 5 * 60 * 1000,
@@ -58,43 +58,23 @@ export const useAssets = () => {
58
58
  clearErrors();
59
59
  setUploading(true);
60
60
 
61
- // Calculate SHA256 for progress tracking
62
- const sha256 = await oxyInstance.calculateSHA256(file);
63
-
64
- // Initialize progress tracking
65
- const initialProgress: AssetUploadProgress = {
66
- fileId: '', // Will be set after init
67
- uploaded: 0,
68
- total: file.size,
69
- percentage: 0,
70
- status: 'uploading'
71
- };
72
-
73
- // Upload with progress callback (visibility undefined, metadata, then onProgress)
74
- const result = await oxyInstance.assetUpload(file as any, undefined, metadata, (percentage: number) => {
75
- if (initialProgress.fileId) {
76
- setUploadProgress(initialProgress.fileId, {
77
- ...initialProgress,
78
- uploaded: Math.round((percentage / 100) * file.size),
79
- percentage,
80
- status: percentage < 100 ? 'uploading' : 'processing'
81
- });
82
- }
83
- });
61
+ // Upload file (progress tracking simplified for now)
62
+ const result = await oxyInstance.assetUpload(file as any, undefined, metadata);
84
63
 
85
64
  // Update progress with final status
86
- if (result.file && initialProgress.fileId) {
87
- setUploadProgress(initialProgress.fileId, {
88
- ...initialProgress,
89
- fileId: result.file.id,
65
+ if (result?.file) {
66
+ const fileId = result.file.id;
67
+ setUploadProgress(fileId, {
68
+ fileId,
90
69
  uploaded: file.size,
70
+ total: file.size,
91
71
  percentage: 100,
92
72
  status: 'complete'
93
73
  });
94
74
 
95
75
  // Remove progress after a short delay
96
76
  setTimeout(() => {
97
- removeUploadProgress(result.file.id);
77
+ removeUploadProgress(fileId);
98
78
  }, 2000);
99
79
  }
100
80
 
@@ -14,6 +14,7 @@ import {
14
14
  Alert,
15
15
  } from 'react-native';
16
16
  import { Image as ExpoImage } from 'expo-image';
17
+ import * as DocumentPicker from 'expo-document-picker';
17
18
  import type { FileManagementScreenProps } from '../types/fileManagement';
18
19
  import { toast } from '../../lib/sonner';
19
20
  import { Ionicons } from '@expo/vector-icons';
@@ -28,13 +29,13 @@ import { useThemeStyles } from '../hooks/useThemeStyles';
28
29
  import { useColorScheme } from '../hooks/use-color-scheme';
29
30
  import { normalizeTheme } from '../utils/themeUtils';
30
31
  import { useOxy } from '../context/OxyContext';
32
+ import { useUploadFile } from '../hooks/mutations/useAccountMutations';
31
33
  import {
32
34
  confirmAction,
33
35
  convertDocumentPickerAssetToFile,
34
36
  formatFileSize,
35
37
  getFileIcon,
36
38
  getSafeDownloadUrl,
37
- uploadFileRaw
38
39
  } from '../utils/fileManagement';
39
40
  import { FileViewer } from '../components/fileManagement/FileViewer';
40
41
  import { FileDetailsModal } from '../components/fileManagement/FileDetailsModal';
@@ -115,6 +116,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
115
116
  }) => {
116
117
  // Use useOxy() hook for OxyContext values
117
118
  const { user, oxyServices } = useOxy();
119
+ const uploadFileMutation = useUploadFile();
118
120
  const files = useFiles();
119
121
  // Ensure containerWidth is a number (TypeScript guard)
120
122
  const safeContainerWidth: number = typeof containerWidth === 'number' ? containerWidth : 400;
@@ -528,7 +530,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
528
530
  };
529
531
  useFileStore.getState().addFile(optimisticFile, { prepend: true });
530
532
 
531
- const result = await uploadFileRaw(raw, targetUserId, oxyServices, defaultVisibility);
533
+ // Use the mutation hook with authentication handling
534
+ const result = await uploadFileMutation.mutateAsync({
535
+ file: raw,
536
+ visibility: defaultVisibility,
537
+ });
532
538
 
533
539
  // Attempt to refresh file list incrementally – fetch single file metadata if API allows
534
540
  if (result?.file || result?.files?.[0]) {
@@ -764,15 +770,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
764
770
  try {
765
771
  setIsPickingDocument(true);
766
772
 
767
- // Dynamically import expo-document-picker (Expo 54 supports it on all platforms)
768
- const DocumentPicker = await import('expo-document-picker').catch(() => null);
769
-
770
- if (!DocumentPicker || !DocumentPicker.getDocumentAsync) {
771
- toast.error('File picker not available. Please install expo-document-picker');
772
- return;
773
- }
774
-
775
- // Use getDocumentAsync directly - it will handle platform availability
773
+ // Use expo-document-picker (works on all platforms including web)
774
+ // On web, it uses the native file input and provides File objects directly
776
775
  const result = await DocumentPicker.getDocumentAsync({
777
776
  type: '*/*',
778
777
  multiple: true,