@oxyhq/auth 1.0.0

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 (119) hide show
  1. package/README.md +56 -0
  2. package/dist/cjs/WebOxyProvider.js +287 -0
  3. package/dist/cjs/hooks/mutations/index.js +23 -0
  4. package/dist/cjs/hooks/mutations/mutationFactory.js +126 -0
  5. package/dist/cjs/hooks/mutations/useAccountMutations.js +275 -0
  6. package/dist/cjs/hooks/mutations/useServicesMutations.js +149 -0
  7. package/dist/cjs/hooks/queries/index.js +35 -0
  8. package/dist/cjs/hooks/queries/queryKeys.js +82 -0
  9. package/dist/cjs/hooks/queries/useAccountQueries.js +141 -0
  10. package/dist/cjs/hooks/queries/useSecurityQueries.js +45 -0
  11. package/dist/cjs/hooks/queries/useServicesQueries.js +113 -0
  12. package/dist/cjs/hooks/queryClient.js +110 -0
  13. package/dist/cjs/hooks/useAssets.js +225 -0
  14. package/dist/cjs/hooks/useFileDownloadUrl.js +91 -0
  15. package/dist/cjs/hooks/useFileFiltering.js +81 -0
  16. package/dist/cjs/hooks/useFollow.js +159 -0
  17. package/dist/cjs/hooks/useFollow.types.js +4 -0
  18. package/dist/cjs/hooks/useQueryClient.js +16 -0
  19. package/dist/cjs/hooks/useSessionSocket.js +215 -0
  20. package/dist/cjs/hooks/useWebSSO.js +146 -0
  21. package/dist/cjs/index.js +115 -0
  22. package/dist/cjs/stores/accountStore.js +226 -0
  23. package/dist/cjs/stores/assetStore.js +192 -0
  24. package/dist/cjs/stores/authStore.js +47 -0
  25. package/dist/cjs/stores/followStore.js +154 -0
  26. package/dist/cjs/utils/authHelpers.js +154 -0
  27. package/dist/cjs/utils/avatarUtils.js +77 -0
  28. package/dist/cjs/utils/errorHandlers.js +128 -0
  29. package/dist/cjs/utils/sessionHelpers.js +90 -0
  30. package/dist/cjs/utils/storageHelpers.js +147 -0
  31. package/dist/esm/WebOxyProvider.js +282 -0
  32. package/dist/esm/hooks/mutations/index.js +10 -0
  33. package/dist/esm/hooks/mutations/mutationFactory.js +122 -0
  34. package/dist/esm/hooks/mutations/useAccountMutations.js +267 -0
  35. package/dist/esm/hooks/mutations/useServicesMutations.js +141 -0
  36. package/dist/esm/hooks/queries/index.js +14 -0
  37. package/dist/esm/hooks/queries/queryKeys.js +76 -0
  38. package/dist/esm/hooks/queries/useAccountQueries.js +131 -0
  39. package/dist/esm/hooks/queries/useSecurityQueries.js +40 -0
  40. package/dist/esm/hooks/queries/useServicesQueries.js +105 -0
  41. package/dist/esm/hooks/queryClient.js +104 -0
  42. package/dist/esm/hooks/useAssets.js +220 -0
  43. package/dist/esm/hooks/useFileDownloadUrl.js +86 -0
  44. package/dist/esm/hooks/useFileFiltering.js +78 -0
  45. package/dist/esm/hooks/useFollow.js +154 -0
  46. package/dist/esm/hooks/useFollow.types.js +3 -0
  47. package/dist/esm/hooks/useQueryClient.js +12 -0
  48. package/dist/esm/hooks/useSessionSocket.js +209 -0
  49. package/dist/esm/hooks/useWebSSO.js +143 -0
  50. package/dist/esm/index.js +48 -0
  51. package/dist/esm/stores/accountStore.js +219 -0
  52. package/dist/esm/stores/assetStore.js +180 -0
  53. package/dist/esm/stores/authStore.js +44 -0
  54. package/dist/esm/stores/followStore.js +151 -0
  55. package/dist/esm/utils/authHelpers.js +145 -0
  56. package/dist/esm/utils/avatarUtils.js +72 -0
  57. package/dist/esm/utils/errorHandlers.js +121 -0
  58. package/dist/esm/utils/sessionHelpers.js +84 -0
  59. package/dist/esm/utils/storageHelpers.js +108 -0
  60. package/dist/types/WebOxyProvider.d.ts +97 -0
  61. package/dist/types/hooks/mutations/index.d.ts +8 -0
  62. package/dist/types/hooks/mutations/mutationFactory.d.ts +75 -0
  63. package/dist/types/hooks/mutations/useAccountMutations.d.ts +68 -0
  64. package/dist/types/hooks/mutations/useServicesMutations.d.ts +22 -0
  65. package/dist/types/hooks/queries/index.d.ts +10 -0
  66. package/dist/types/hooks/queries/queryKeys.d.ts +64 -0
  67. package/dist/types/hooks/queries/useAccountQueries.d.ts +42 -0
  68. package/dist/types/hooks/queries/useSecurityQueries.d.ts +14 -0
  69. package/dist/types/hooks/queries/useServicesQueries.d.ts +31 -0
  70. package/dist/types/hooks/queryClient.d.ts +18 -0
  71. package/dist/types/hooks/useAssets.d.ts +34 -0
  72. package/dist/types/hooks/useFileDownloadUrl.d.ts +18 -0
  73. package/dist/types/hooks/useFileFiltering.d.ts +28 -0
  74. package/dist/types/hooks/useFollow.d.ts +61 -0
  75. package/dist/types/hooks/useFollow.types.d.ts +32 -0
  76. package/dist/types/hooks/useQueryClient.d.ts +6 -0
  77. package/dist/types/hooks/useSessionSocket.d.ts +13 -0
  78. package/dist/types/hooks/useWebSSO.d.ts +57 -0
  79. package/dist/types/index.d.ts +46 -0
  80. package/dist/types/stores/accountStore.d.ts +33 -0
  81. package/dist/types/stores/assetStore.d.ts +53 -0
  82. package/dist/types/stores/authStore.d.ts +16 -0
  83. package/dist/types/stores/followStore.d.ts +24 -0
  84. package/dist/types/utils/authHelpers.d.ts +98 -0
  85. package/dist/types/utils/avatarUtils.d.ts +33 -0
  86. package/dist/types/utils/errorHandlers.d.ts +34 -0
  87. package/dist/types/utils/sessionHelpers.d.ts +63 -0
  88. package/dist/types/utils/storageHelpers.d.ts +27 -0
  89. package/package.json +71 -0
  90. package/src/WebOxyProvider.tsx +372 -0
  91. package/src/global.d.ts +1 -0
  92. package/src/hooks/mutations/index.ts +25 -0
  93. package/src/hooks/mutations/mutationFactory.ts +215 -0
  94. package/src/hooks/mutations/useAccountMutations.ts +344 -0
  95. package/src/hooks/mutations/useServicesMutations.ts +164 -0
  96. package/src/hooks/queries/index.ts +36 -0
  97. package/src/hooks/queries/queryKeys.ts +88 -0
  98. package/src/hooks/queries/useAccountQueries.ts +152 -0
  99. package/src/hooks/queries/useSecurityQueries.ts +64 -0
  100. package/src/hooks/queries/useServicesQueries.ts +126 -0
  101. package/src/hooks/queryClient.ts +112 -0
  102. package/src/hooks/useAssets.ts +291 -0
  103. package/src/hooks/useFileDownloadUrl.ts +118 -0
  104. package/src/hooks/useFileFiltering.ts +115 -0
  105. package/src/hooks/useFollow.ts +175 -0
  106. package/src/hooks/useFollow.types.ts +33 -0
  107. package/src/hooks/useQueryClient.ts +17 -0
  108. package/src/hooks/useSessionSocket.ts +233 -0
  109. package/src/hooks/useWebSSO.ts +187 -0
  110. package/src/index.ts +144 -0
  111. package/src/stores/accountStore.ts +296 -0
  112. package/src/stores/assetStore.ts +281 -0
  113. package/src/stores/authStore.ts +63 -0
  114. package/src/stores/followStore.ts +181 -0
  115. package/src/utils/authHelpers.ts +183 -0
  116. package/src/utils/avatarUtils.ts +103 -0
  117. package/src/utils/errorHandlers.ts +194 -0
  118. package/src/utils/sessionHelpers.ts +151 -0
  119. package/src/utils/storageHelpers.ts +130 -0
@@ -0,0 +1,344 @@
1
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
2
+ import type { User } from '@oxyhq/core';
3
+ import { queryKeys, invalidateAccountQueries, invalidateUserQueries } from '../queries/queryKeys';
4
+ import { useWebOxy } from '../../WebOxyProvider';
5
+ import { toast } from 'sonner';
6
+ import { refreshAvatarInStore } from '../../utils/avatarUtils';
7
+ import { useAuthStore } from '../../stores/authStore';
8
+ import { authenticatedApiCall } from '../../utils/authHelpers';
9
+
10
+ /**
11
+ * Update user profile with optimistic updates and offline queue support
12
+ */
13
+ export const useUpdateProfile = () => {
14
+ const { oxyServices, activeSessionId, user } = useWebOxy();
15
+ const queryClient = useQueryClient();
16
+
17
+ return useMutation({
18
+ mutationFn: async (updates: Partial<User>) => {
19
+ return authenticatedApiCall<User>(
20
+ oxyServices,
21
+ activeSessionId,
22
+ () => oxyServices.updateProfile(updates)
23
+ );
24
+ },
25
+ // Optimistic update
26
+ onMutate: async (updates) => {
27
+ // Cancel outgoing refetches
28
+ await queryClient.cancelQueries({ queryKey: queryKeys.accounts.current() });
29
+
30
+ // Snapshot previous value
31
+ const previousUser = queryClient.getQueryData<User>(queryKeys.accounts.current());
32
+
33
+ // Optimistically update
34
+ if (previousUser) {
35
+ queryClient.setQueryData<User>(queryKeys.accounts.current(), {
36
+ ...previousUser,
37
+ ...updates,
38
+ });
39
+
40
+ // Also update profile query if sessionId is available
41
+ if (activeSessionId) {
42
+ queryClient.setQueryData<User>(queryKeys.users.profile(activeSessionId), {
43
+ ...previousUser,
44
+ ...updates,
45
+ });
46
+ }
47
+ }
48
+
49
+ return { previousUser };
50
+ },
51
+ // On error, rollback
52
+ onError: (error, updates, context) => {
53
+ if (context?.previousUser) {
54
+ queryClient.setQueryData(queryKeys.accounts.current(), context.previousUser);
55
+ if (activeSessionId) {
56
+ queryClient.setQueryData(queryKeys.users.profile(activeSessionId), context.previousUser);
57
+ }
58
+ }
59
+ toast.error(error instanceof Error ? error.message : 'Failed to update profile');
60
+ },
61
+ // On success, invalidate and refetch
62
+ onSuccess: (data, updates) => {
63
+ // Update cache with server response
64
+ queryClient.setQueryData(queryKeys.accounts.current(), data);
65
+ if (activeSessionId) {
66
+ queryClient.setQueryData(queryKeys.users.profile(activeSessionId), data);
67
+ }
68
+
69
+ // Update authStore so frontend components see the changes immediately
70
+ useAuthStore.getState().setUser(data);
71
+
72
+ // If avatar was updated, refresh accountStore with cache-busted URL
73
+ if (updates.avatar && activeSessionId && oxyServices) {
74
+ refreshAvatarInStore(activeSessionId, updates.avatar, oxyServices);
75
+ }
76
+
77
+ // Invalidate all related queries to refresh everywhere
78
+ invalidateUserQueries(queryClient);
79
+ invalidateAccountQueries(queryClient);
80
+ },
81
+ });
82
+ };
83
+
84
+ /**
85
+ * Upload avatar with progress tracking and offline queue support
86
+ */
87
+ export const useUploadAvatar = () => {
88
+ const { oxyServices, activeSessionId } = useWebOxy();
89
+ const queryClient = useQueryClient();
90
+
91
+ return useMutation({
92
+ mutationFn: async (file: { uri: string; type?: string; name?: string; size?: number }) => {
93
+ return authenticatedApiCall<User>(oxyServices, activeSessionId, async () => {
94
+ // Upload file first
95
+ const uploadResult = await oxyServices.assetUpload(file as any, 'public');
96
+ const fileId = uploadResult?.file?.id || uploadResult?.id || uploadResult;
97
+
98
+ if (!fileId || typeof fileId !== 'string') {
99
+ throw new Error('Failed to get file ID from upload result');
100
+ }
101
+
102
+ // Update profile with file ID
103
+ return await oxyServices.updateProfile({ avatar: fileId });
104
+ });
105
+ },
106
+ onMutate: async (file) => {
107
+ await queryClient.cancelQueries({ queryKey: queryKeys.accounts.current() });
108
+ const previousUser = queryClient.getQueryData<User>(queryKeys.accounts.current());
109
+
110
+ // Optimistically set a temporary avatar (using file URI as placeholder)
111
+ if (previousUser) {
112
+ const optimisticUser = {
113
+ ...previousUser,
114
+ avatar: file.uri, // Temporary, will be replaced with fileId
115
+ };
116
+ queryClient.setQueryData<User>(queryKeys.accounts.current(), optimisticUser);
117
+ if (activeSessionId) {
118
+ queryClient.setQueryData<User>(queryKeys.users.profile(activeSessionId), optimisticUser);
119
+ }
120
+ }
121
+
122
+ return { previousUser };
123
+ },
124
+ onError: (error, file, context) => {
125
+ if (context?.previousUser) {
126
+ queryClient.setQueryData(queryKeys.accounts.current(), context.previousUser);
127
+ if (activeSessionId) {
128
+ queryClient.setQueryData(queryKeys.users.profile(activeSessionId), context.previousUser);
129
+ }
130
+ }
131
+ toast.error(error instanceof Error ? error.message : 'Failed to upload avatar');
132
+ },
133
+ onSuccess: (data) => {
134
+ queryClient.setQueryData(queryKeys.accounts.current(), data);
135
+ if (activeSessionId) {
136
+ queryClient.setQueryData(queryKeys.users.profile(activeSessionId), data);
137
+ }
138
+
139
+ // Update authStore so frontend components see the changes immediately
140
+ useAuthStore.getState().setUser(data);
141
+
142
+ // Refresh accountStore with cache-busted URL if avatar was updated
143
+ if (data?.avatar && activeSessionId && oxyServices) {
144
+ refreshAvatarInStore(activeSessionId, data.avatar, oxyServices);
145
+ }
146
+
147
+ // Invalidate all related queries to refresh everywhere
148
+ invalidateUserQueries(queryClient);
149
+ invalidateAccountQueries(queryClient);
150
+ toast.success('Avatar updated successfully');
151
+ },
152
+ });
153
+ };
154
+
155
+ /**
156
+ * Update account settings
157
+ */
158
+ export const useUpdateAccountSettings = () => {
159
+ const { oxyServices, activeSessionId } = useWebOxy();
160
+ const queryClient = useQueryClient();
161
+
162
+ return useMutation({
163
+ mutationFn: async (settings: Record<string, any>) => {
164
+ return await oxyServices.updateProfile({ privacySettings: settings });
165
+ },
166
+ onMutate: async (settings) => {
167
+ await queryClient.cancelQueries({ queryKey: queryKeys.accounts.settings() });
168
+ const previousUser = queryClient.getQueryData<User>(queryKeys.accounts.current());
169
+
170
+ if (previousUser) {
171
+ queryClient.setQueryData<User>(queryKeys.accounts.current(), {
172
+ ...previousUser,
173
+ privacySettings: {
174
+ ...previousUser.privacySettings,
175
+ ...settings,
176
+ },
177
+ });
178
+ }
179
+
180
+ return { previousUser };
181
+ },
182
+ onError: (error, settings, context) => {
183
+ if (context?.previousUser) {
184
+ queryClient.setQueryData(queryKeys.accounts.current(), context.previousUser);
185
+ }
186
+ toast.error(error instanceof Error ? error.message : 'Failed to update settings');
187
+ },
188
+ onSuccess: (data) => {
189
+ queryClient.setQueryData(queryKeys.accounts.current(), data);
190
+
191
+ // Update authStore so frontend components see the changes immediately
192
+ useAuthStore.getState().setUser(data);
193
+
194
+ invalidateAccountQueries(queryClient);
195
+ toast.success('Settings updated successfully');
196
+ },
197
+ onSettled: () => {
198
+ queryClient.invalidateQueries({ queryKey: queryKeys.accounts.settings() });
199
+ },
200
+ });
201
+ };
202
+
203
+ /**
204
+ * Update privacy settings with optimistic updates and authentication handling
205
+ */
206
+ export const useUpdatePrivacySettings = () => {
207
+ const { oxyServices, activeSessionId, user } = useWebOxy();
208
+ const queryClient = useQueryClient();
209
+
210
+ return useMutation({
211
+ mutationFn: async ({ settings, userId }: { settings: Record<string, any>; userId?: string }) => {
212
+ const targetUserId = userId || user?.id;
213
+ if (!targetUserId) {
214
+ throw new Error('User ID is required');
215
+ }
216
+
217
+ return authenticatedApiCall<Record<string, unknown>>(
218
+ oxyServices,
219
+ activeSessionId,
220
+ () => oxyServices.updatePrivacySettings(settings, targetUserId)
221
+ );
222
+ },
223
+ // Optimistic update
224
+ onMutate: async ({ settings, userId }) => {
225
+ const targetUserId = userId || user?.id;
226
+ if (!targetUserId) return;
227
+
228
+ // Cancel outgoing refetches
229
+ await queryClient.cancelQueries({ queryKey: queryKeys.privacy.settings(targetUserId) });
230
+ await queryClient.cancelQueries({ queryKey: queryKeys.accounts.current() });
231
+
232
+ // Snapshot previous values
233
+ const previousPrivacySettings = queryClient.getQueryData(queryKeys.privacy.settings(targetUserId));
234
+ const previousUser = queryClient.getQueryData<User>(queryKeys.accounts.current());
235
+
236
+ // Optimistically update privacy settings
237
+ if (previousPrivacySettings) {
238
+ queryClient.setQueryData(queryKeys.privacy.settings(targetUserId), {
239
+ ...previousPrivacySettings,
240
+ ...settings,
241
+ });
242
+ }
243
+
244
+ // Also update user query if available
245
+ if (previousUser) {
246
+ queryClient.setQueryData<User>(queryKeys.accounts.current(), {
247
+ ...previousUser,
248
+ privacySettings: {
249
+ ...previousUser.privacySettings,
250
+ ...settings,
251
+ },
252
+ });
253
+ }
254
+
255
+ return { previousPrivacySettings, previousUser };
256
+ },
257
+ // On error, rollback
258
+ onError: (error, { userId }, context) => {
259
+ const targetUserId = userId || user?.id;
260
+ if (context?.previousPrivacySettings && targetUserId) {
261
+ queryClient.setQueryData(queryKeys.privacy.settings(targetUserId), context.previousPrivacySettings);
262
+ }
263
+ if (context?.previousUser) {
264
+ queryClient.setQueryData(queryKeys.accounts.current(), context.previousUser);
265
+ }
266
+ toast.error(error instanceof Error ? error.message : 'Failed to update privacy settings');
267
+ },
268
+ // On success, invalidate and refetch
269
+ onSuccess: (data, { userId }) => {
270
+ const targetUserId = userId || user?.id;
271
+ if (targetUserId) {
272
+ queryClient.setQueryData(queryKeys.privacy.settings(targetUserId), data);
273
+ }
274
+ // Also update account query if it contains privacy settings
275
+ const currentUser = queryClient.getQueryData<User>(queryKeys.accounts.current());
276
+ if (currentUser) {
277
+ const updatedUser: User = {
278
+ ...currentUser,
279
+ privacySettings: data as { [key: string]: unknown },
280
+ };
281
+ queryClient.setQueryData<User>(queryKeys.accounts.current(), updatedUser);
282
+
283
+ // Update authStore so frontend components see the changes immediately
284
+ useAuthStore.getState().setUser(updatedUser);
285
+ }
286
+ invalidateAccountQueries(queryClient);
287
+ },
288
+ // Always refetch after error or success
289
+ onSettled: (data, error, { userId }) => {
290
+ const targetUserId = userId || user?.id;
291
+ if (targetUserId) {
292
+ queryClient.invalidateQueries({ queryKey: queryKeys.privacy.settings(targetUserId) });
293
+ }
294
+ queryClient.invalidateQueries({ queryKey: queryKeys.accounts.current() });
295
+ },
296
+ });
297
+ };
298
+
299
+ /** Uploaded file data structure from API */
300
+ interface UploadedFile {
301
+ id: string;
302
+ originalName?: string;
303
+ sha256?: string;
304
+ mime?: string;
305
+ size?: number;
306
+ createdAt?: string;
307
+ metadata?: Record<string, unknown>;
308
+ variants?: Array<{ type: string; key: string; width?: number; height?: number; readyAt?: string; metadata?: Record<string, unknown> }>;
309
+ }
310
+
311
+ /** Upload result type that supports both single file and batch responses */
312
+ interface UploadResult {
313
+ file?: UploadedFile;
314
+ files?: UploadedFile[];
315
+ id?: string;
316
+ }
317
+
318
+ /**
319
+ * Upload file with authentication handling and progress tracking
320
+ */
321
+ export const useUploadFile = () => {
322
+ const { oxyServices, activeSessionId } = useWebOxy();
323
+
324
+ return useMutation({
325
+ mutationFn: async ({
326
+ file,
327
+ visibility,
328
+ metadata,
329
+ onProgress
330
+ }: {
331
+ file: File;
332
+ visibility?: 'private' | 'public' | 'unlisted';
333
+ metadata?: Record<string, any>;
334
+ onProgress?: (progress: number) => void;
335
+ }) => {
336
+ return authenticatedApiCall<UploadResult>(
337
+ oxyServices,
338
+ activeSessionId,
339
+ () => oxyServices.assetUpload(file as any, visibility, metadata, onProgress)
340
+ );
341
+ },
342
+ });
343
+ };
344
+
@@ -0,0 +1,164 @@
1
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
2
+ import type { User } from '@oxyhq/core';
3
+ import { queryKeys, invalidateSessionQueries } from '../queries/queryKeys';
4
+ import { useWebOxy } from '../../WebOxyProvider';
5
+ import { toast } from 'sonner';
6
+
7
+ /**
8
+ * Switch active session
9
+ */
10
+ export const useSwitchSession = () => {
11
+ const { switchSession, activeSessionId } = useWebOxy();
12
+ const queryClient = useQueryClient();
13
+
14
+ return useMutation({
15
+ mutationFn: async (sessionId: string) => {
16
+ return await switchSession(sessionId);
17
+ },
18
+ onSuccess: (user) => {
19
+ // Invalidate all session queries
20
+ invalidateSessionQueries(queryClient);
21
+
22
+ // Update current user query
23
+ queryClient.setQueryData(queryKeys.accounts.current(), user);
24
+
25
+ // Invalidate account queries
26
+ queryClient.invalidateQueries({ queryKey: queryKeys.accounts.all });
27
+ },
28
+ onError: (error) => {
29
+ toast.error(error instanceof Error ? error.message : 'Failed to switch session');
30
+ },
31
+ });
32
+ };
33
+
34
+ /**
35
+ * Logout from a session
36
+ */
37
+ export const useLogoutSession = () => {
38
+ const { oxyServices, activeSessionId, sessions } = useWebOxy();
39
+ const queryClient = useQueryClient();
40
+
41
+ return useMutation({
42
+ mutationFn: async (targetSessionId?: string) => {
43
+ if (!activeSessionId) {
44
+ throw new Error('No active session');
45
+ }
46
+
47
+ const sessionToLogout = targetSessionId || activeSessionId;
48
+ await oxyServices.logoutSession(activeSessionId, sessionToLogout);
49
+
50
+ return sessionToLogout;
51
+ },
52
+ onMutate: async (targetSessionId) => {
53
+ // Cancel outgoing queries
54
+ await queryClient.cancelQueries({ queryKey: queryKeys.sessions.all });
55
+
56
+ // Snapshot previous sessions
57
+ const previousSessions = queryClient.getQueryData(queryKeys.sessions.list());
58
+
59
+ // Optimistically remove session
60
+ if (previousSessions) {
61
+ const sessionToLogout = targetSessionId || activeSessionId;
62
+ const updatedSessions = (previousSessions as any[]).filter(
63
+ (s: any) => s.sessionId !== sessionToLogout
64
+ );
65
+ queryClient.setQueryData(queryKeys.sessions.list(), updatedSessions);
66
+ }
67
+
68
+ return { previousSessions };
69
+ },
70
+ onError: (error, targetSessionId, context) => {
71
+ // Rollback on error
72
+ if (context?.previousSessions) {
73
+ queryClient.setQueryData(queryKeys.sessions.list(), context.previousSessions);
74
+ }
75
+ toast.error(error instanceof Error ? error.message : 'Failed to logout');
76
+ },
77
+ onSuccess: () => {
78
+ // Invalidate all session queries
79
+ invalidateSessionQueries(queryClient);
80
+ },
81
+ onSettled: () => {
82
+ queryClient.invalidateQueries({ queryKey: queryKeys.sessions.all });
83
+ },
84
+ });
85
+ };
86
+
87
+ /**
88
+ * Logout from all sessions
89
+ */
90
+ export const useLogoutAll = () => {
91
+ const { oxyServices, activeSessionId, clearSessionState } = useWebOxy();
92
+ const queryClient = useQueryClient();
93
+
94
+ return useMutation({
95
+ mutationFn: async () => {
96
+ if (!activeSessionId) {
97
+ throw new Error('No active session');
98
+ }
99
+
100
+ await oxyServices.logoutAllSessions(activeSessionId);
101
+ await clearSessionState();
102
+ },
103
+ onSuccess: () => {
104
+ // Clear all queries
105
+ queryClient.clear();
106
+ toast.success('Logged out from all sessions');
107
+ },
108
+ onError: (error) => {
109
+ toast.error(error instanceof Error ? error.message : 'Failed to logout from all sessions');
110
+ },
111
+ });
112
+ };
113
+
114
+ /**
115
+ * Update device name
116
+ */
117
+ export const useUpdateDeviceName = () => {
118
+ const { oxyServices, activeSessionId } = useWebOxy();
119
+ const queryClient = useQueryClient();
120
+
121
+ return useMutation({
122
+ mutationFn: async (deviceName: string) => {
123
+ if (!activeSessionId) {
124
+ throw new Error('No active session');
125
+ }
126
+
127
+ return await oxyServices.updateDeviceName(activeSessionId, deviceName);
128
+ },
129
+ onSuccess: () => {
130
+ // Invalidate device and session queries
131
+ queryClient.invalidateQueries({ queryKey: queryKeys.devices.all });
132
+ queryClient.invalidateQueries({ queryKey: queryKeys.sessions.all });
133
+ toast.success('Device name updated');
134
+ },
135
+ onError: (error) => {
136
+ toast.error(error instanceof Error ? error.message : 'Failed to update device name');
137
+ },
138
+ });
139
+ };
140
+
141
+ /**
142
+ * Remove a device
143
+ */
144
+ export const useRemoveDevice = () => {
145
+ const { oxyServices } = useWebOxy();
146
+ const queryClient = useQueryClient();
147
+
148
+ return useMutation({
149
+ mutationFn: async (deviceId: string) => {
150
+ await oxyServices.removeDevice(deviceId);
151
+ return deviceId;
152
+ },
153
+ onSuccess: () => {
154
+ // Invalidate device queries
155
+ queryClient.invalidateQueries({ queryKey: queryKeys.devices.all });
156
+ queryClient.invalidateQueries({ queryKey: queryKeys.sessions.all });
157
+ toast.success('Device removed');
158
+ },
159
+ onError: (error) => {
160
+ toast.error(error instanceof Error ? error.message : 'Failed to remove device');
161
+ },
162
+ });
163
+ };
164
+
@@ -0,0 +1,36 @@
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
+ // Security activity query hooks
29
+ export {
30
+ useSecurityActivity,
31
+ useRecentSecurityActivity,
32
+ } from './useSecurityQueries';
33
+
34
+ // Query keys and invalidation helpers (for advanced usage)
35
+ export { queryKeys, invalidateAccountQueries, invalidateUserQueries, invalidateSessionQueries } from './queryKeys';
36
+
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Centralized query keys for TanStack Query
3
+ *
4
+ * Following best practices:
5
+ * - Use arrays for hierarchical keys
6
+ * - Include all parameters in the key
7
+ * - Use consistent naming conventions
8
+ */
9
+
10
+ export const queryKeys = {
11
+ // Account queries
12
+ accounts: {
13
+ all: ['accounts'] as const,
14
+ lists: () => [...queryKeys.accounts.all, 'list'] as const,
15
+ list: (sessionIds: string[]) => [...queryKeys.accounts.lists(), sessionIds] as const,
16
+ details: () => [...queryKeys.accounts.all, 'detail'] as const,
17
+ detail: (sessionId: string) => [...queryKeys.accounts.details(), sessionId] as const,
18
+ current: () => [...queryKeys.accounts.all, 'current'] as const,
19
+ settings: () => [...queryKeys.accounts.all, 'settings'] as const,
20
+ },
21
+
22
+ // User queries
23
+ users: {
24
+ all: ['users'] as const,
25
+ lists: () => [...queryKeys.users.all, 'list'] as const,
26
+ list: (userIds: string[]) => [...queryKeys.users.lists(), userIds] as const,
27
+ details: () => [...queryKeys.users.all, 'detail'] as const,
28
+ detail: (userId: string) => [...queryKeys.users.details(), userId] as const,
29
+ profile: (sessionId: string) => [...queryKeys.users.details(), sessionId, 'profile'] as const,
30
+ },
31
+
32
+ // Session queries
33
+ sessions: {
34
+ all: ['sessions'] as const,
35
+ lists: () => [...queryKeys.sessions.all, 'list'] as const,
36
+ list: (userId?: string) => [...queryKeys.sessions.lists(), userId] as const,
37
+ details: () => [...queryKeys.sessions.all, 'detail'] as const,
38
+ detail: (sessionId: string) => [...queryKeys.sessions.details(), sessionId] as const,
39
+ active: () => [...queryKeys.sessions.all, 'active'] as const,
40
+ device: (deviceId: string) => [...queryKeys.sessions.all, 'device', deviceId] as const,
41
+ },
42
+
43
+ // Device queries
44
+ devices: {
45
+ all: ['devices'] as const,
46
+ lists: () => [...queryKeys.devices.all, 'list'] as const,
47
+ list: (userId?: string) => [...queryKeys.devices.lists(), userId] as const,
48
+ details: () => [...queryKeys.devices.all, 'detail'] as const,
49
+ detail: (deviceId: string) => [...queryKeys.devices.details(), deviceId] as const,
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
+ },
57
+
58
+ // Security activity queries
59
+ security: {
60
+ all: ['security'] as const,
61
+ activity: (limit?: number, offset?: number, eventType?: string) =>
62
+ [...queryKeys.security.all, 'activity', limit, offset, eventType] as const,
63
+ recent: (limit: number) =>
64
+ [...queryKeys.security.all, 'recent', limit] as const,
65
+ },
66
+ } as const;
67
+
68
+ /**
69
+ * Helper to invalidate all account-related queries
70
+ */
71
+ export const invalidateAccountQueries = (queryClient: any) => {
72
+ queryClient.invalidateQueries({ queryKey: queryKeys.accounts.all });
73
+ };
74
+
75
+ /**
76
+ * Helper to invalidate all user-related queries
77
+ */
78
+ export const invalidateUserQueries = (queryClient: any) => {
79
+ queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
80
+ };
81
+
82
+ /**
83
+ * Helper to invalidate all session-related queries
84
+ */
85
+ export const invalidateSessionQueries = (queryClient: any) => {
86
+ queryClient.invalidateQueries({ queryKey: queryKeys.sessions.all });
87
+ };
88
+