@oxyhq/auth 2.0.4 → 2.0.5
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.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/WebOxyProvider.js +37 -0
- package/dist/cjs/hooks/mutations/useAccountMutations.js +185 -43
- package/dist/cjs/hooks/queryClient.js +136 -92
- package/dist/cjs/hooks/useFileDownloadUrl.js +12 -36
- package/dist/cjs/hooks/useSessionSocket.js +81 -94
- package/dist/cjs/index.js +1 -2
- package/dist/cjs/utils/sessionHelpers.js +3 -1
- package/dist/cjs/utils/storageHelpers.js +36 -10
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/WebOxyProvider.js +38 -1
- package/dist/esm/hooks/mutations/useAccountMutations.js +186 -44
- package/dist/esm/hooks/queryClient.js +132 -89
- package/dist/esm/hooks/useFileDownloadUrl.js +11 -34
- package/dist/esm/hooks/useSessionSocket.js +81 -94
- package/dist/esm/index.js +1 -1
- package/dist/esm/utils/sessionHelpers.js +3 -1
- package/dist/esm/utils/storageHelpers.js +36 -10
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/WebOxyProvider.d.ts +1 -1
- package/dist/types/hooks/mutations/useAccountMutations.d.ts +153 -9
- package/dist/types/hooks/queries/useAccountQueries.d.ts +11 -7
- package/dist/types/hooks/queries/useSecurityQueries.d.ts +2 -2
- package/dist/types/hooks/queries/useServicesQueries.d.ts +7 -5
- package/dist/types/hooks/queryClient.d.ts +24 -10
- package/dist/types/hooks/useAssets.d.ts +1 -1
- package/dist/types/hooks/useFileDownloadUrl.d.ts +2 -6
- package/dist/types/index.d.ts +1 -1
- package/dist/types/utils/sessionHelpers.d.ts +3 -1
- package/package.json +22 -3
- package/src/WebOxyProvider.tsx +39 -1
- package/src/hooks/mutations/useAccountMutations.ts +230 -57
- package/src/hooks/queryClient.ts +140 -83
- package/src/hooks/useFileDownloadUrl.ts +15 -39
- package/src/hooks/useSessionSocket.ts +123 -91
- package/src/index.ts +1 -1
- package/src/utils/sessionHelpers.ts +3 -1
- package/src/utils/storageHelpers.ts +49 -10
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
2
2
|
import { authenticatedApiCall } from '@oxyhq/core';
|
|
3
|
-
import type { User } from '@oxyhq/core';
|
|
4
|
-
import { queryKeys, invalidateAccountQueries, invalidateUserQueries } from '../queries/queryKeys';
|
|
3
|
+
import type { PrivacySettings, User } from '@oxyhq/core';
|
|
4
|
+
import { queryKeys, invalidateAccountQueries, invalidateUserQueries, invalidateSessionQueries } from '../queries/queryKeys';
|
|
5
5
|
import { useWebOxy } from '../../WebOxyProvider';
|
|
6
6
|
import { toast } from 'sonner';
|
|
7
7
|
import { refreshAvatarInStore } from '../../utils/avatarUtils';
|
|
@@ -11,7 +11,7 @@ import { useAuthStore } from '../../stores/authStore';
|
|
|
11
11
|
* Update user profile with optimistic updates and offline queue support
|
|
12
12
|
*/
|
|
13
13
|
export const useUpdateProfile = () => {
|
|
14
|
-
const { oxyServices, activeSessionId
|
|
14
|
+
const { oxyServices, activeSessionId } = useWebOxy();
|
|
15
15
|
const queryClient = useQueryClient();
|
|
16
16
|
|
|
17
17
|
return useMutation({
|
|
@@ -48,12 +48,31 @@ export const useUpdateProfile = () => {
|
|
|
48
48
|
|
|
49
49
|
return { previousUser };
|
|
50
50
|
},
|
|
51
|
-
// On error, rollback
|
|
51
|
+
// On error, rollback ONLY the keys this mutation tried to change
|
|
52
52
|
onError: (error, updates, context) => {
|
|
53
|
-
if (context?.previousUser) {
|
|
54
|
-
|
|
53
|
+
if (context?.previousUser && updates) {
|
|
54
|
+
const previousUser = context.previousUser;
|
|
55
|
+
const changedKeys = Object.keys(updates) as Array<keyof User>;
|
|
56
|
+
const partialRollback = changedKeys.reduce<Partial<User>>((acc, key) => {
|
|
57
|
+
(acc as Record<string, unknown>)[key as string] = previousUser[key];
|
|
58
|
+
return acc;
|
|
59
|
+
}, {});
|
|
60
|
+
|
|
61
|
+
const current = queryClient.getQueryData<User>(queryKeys.accounts.current());
|
|
62
|
+
if (current) {
|
|
63
|
+
queryClient.setQueryData<User>(queryKeys.accounts.current(), {
|
|
64
|
+
...current,
|
|
65
|
+
...partialRollback,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
55
68
|
if (activeSessionId) {
|
|
56
|
-
queryClient.
|
|
69
|
+
const currentProfile = queryClient.getQueryData<User>(queryKeys.users.profile(activeSessionId));
|
|
70
|
+
if (currentProfile) {
|
|
71
|
+
queryClient.setQueryData<User>(queryKeys.users.profile(activeSessionId), {
|
|
72
|
+
...currentProfile,
|
|
73
|
+
...partialRollback,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
57
76
|
}
|
|
58
77
|
}
|
|
59
78
|
toast.error(error instanceof Error ? error.message : 'Failed to update profile');
|
|
@@ -65,18 +84,22 @@ export const useUpdateProfile = () => {
|
|
|
65
84
|
if (activeSessionId) {
|
|
66
85
|
queryClient.setQueryData(queryKeys.users.profile(activeSessionId), data);
|
|
67
86
|
}
|
|
68
|
-
|
|
87
|
+
|
|
69
88
|
// Update authStore so frontend components see the changes immediately
|
|
70
89
|
useAuthStore.getState().setUser(data);
|
|
71
|
-
|
|
90
|
+
|
|
72
91
|
// If avatar was updated, refresh accountStore with cache-busted URL
|
|
73
92
|
if (updates.avatar && activeSessionId && oxyServices) {
|
|
74
93
|
refreshAvatarInStore(activeSessionId, updates.avatar, oxyServices);
|
|
75
94
|
}
|
|
76
|
-
|
|
77
|
-
// Invalidate all related queries
|
|
95
|
+
|
|
96
|
+
// Invalidate all related queries so every consumer (AccountSwitcher,
|
|
97
|
+
// session lists, managed accounts, etc.) refetches the fresh profile.
|
|
98
|
+
// Critical right after `username` is set the first time, when every
|
|
99
|
+
// cached "session profile" still reports the user as unnamed.
|
|
78
100
|
invalidateUserQueries(queryClient);
|
|
79
101
|
invalidateAccountQueries(queryClient);
|
|
102
|
+
invalidateSessionQueries(queryClient);
|
|
80
103
|
},
|
|
81
104
|
});
|
|
82
105
|
};
|
|
@@ -122,11 +145,25 @@ export const useUploadAvatar = () => {
|
|
|
122
145
|
|
|
123
146
|
return { previousUser };
|
|
124
147
|
},
|
|
125
|
-
onError: (error,
|
|
148
|
+
onError: (error, _file, context) => {
|
|
149
|
+
// Avatar upload only mutates the `avatar` field — restore only that key
|
|
126
150
|
if (context?.previousUser) {
|
|
127
|
-
|
|
151
|
+
const previousAvatar = context.previousUser.avatar;
|
|
152
|
+
const current = queryClient.getQueryData<User>(queryKeys.accounts.current());
|
|
153
|
+
if (current) {
|
|
154
|
+
queryClient.setQueryData<User>(queryKeys.accounts.current(), {
|
|
155
|
+
...current,
|
|
156
|
+
avatar: previousAvatar,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
128
159
|
if (activeSessionId) {
|
|
129
|
-
queryClient.
|
|
160
|
+
const currentProfile = queryClient.getQueryData<User>(queryKeys.users.profile(activeSessionId));
|
|
161
|
+
if (currentProfile) {
|
|
162
|
+
queryClient.setQueryData<User>(queryKeys.users.profile(activeSessionId), {
|
|
163
|
+
...currentProfile,
|
|
164
|
+
avatar: previousAvatar,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
130
167
|
}
|
|
131
168
|
}
|
|
132
169
|
toast.error(error instanceof Error ? error.message : 'Failed to upload avatar');
|
|
@@ -144,27 +181,69 @@ export const useUploadAvatar = () => {
|
|
|
144
181
|
if (data?.avatar && activeSessionId && oxyServices) {
|
|
145
182
|
refreshAvatarInStore(activeSessionId, data.avatar, oxyServices);
|
|
146
183
|
}
|
|
147
|
-
|
|
148
|
-
// Invalidate all related queries to refresh everywhere
|
|
184
|
+
|
|
185
|
+
// Invalidate all related queries to refresh everywhere, including the
|
|
186
|
+
// sessions cache so other-account avatars update too.
|
|
149
187
|
invalidateUserQueries(queryClient);
|
|
150
188
|
invalidateAccountQueries(queryClient);
|
|
189
|
+
invalidateSessionQueries(queryClient);
|
|
151
190
|
toast.success('Avatar updated successfully');
|
|
152
191
|
},
|
|
153
192
|
});
|
|
154
193
|
};
|
|
155
194
|
|
|
156
195
|
/**
|
|
157
|
-
*
|
|
196
|
+
* Variables accepted by the `useUpdateAccountSettings` mutation.
|
|
197
|
+
*
|
|
198
|
+
* `currentUser` is captured at dispatch time so the rebuilt user object the
|
|
199
|
+
* mutation returns is computed against a stable snapshot — NOT the cache
|
|
200
|
+
* value at the moment the API call settles. Reading from the cache inside
|
|
201
|
+
* `mutationFn` would race with sibling optimistic updates: a concurrent
|
|
202
|
+
* write could already have overwritten the cache by the time the privacy
|
|
203
|
+
* update returns, causing the rebuilt user to clobber the sibling's
|
|
204
|
+
* optimistic value.
|
|
205
|
+
*/
|
|
206
|
+
interface UpdateAccountSettingsVariables {
|
|
207
|
+
updates: Partial<PrivacySettings>;
|
|
208
|
+
currentUser: User;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Update account settings (privacy preferences).
|
|
213
|
+
*
|
|
214
|
+
* Privacy settings are not part of the `PUT /users/me` allow-list; the API
|
|
215
|
+
* would silently drop them. Route through `updatePrivacySettings` so the
|
|
216
|
+
* dedicated `PATCH /privacy/:id/privacy` endpoint performs a dot-path merge
|
|
217
|
+
* and returns the updated `privacySettings` object.
|
|
218
|
+
*
|
|
219
|
+
* The returned object exposes the standard mutation surface PLUS a
|
|
220
|
+
* convenience `mutate(updates)` / `mutateAsync(updates)` that snapshots
|
|
221
|
+
* the current user from `useWebOxy()` at dispatch time.
|
|
158
222
|
*/
|
|
159
223
|
export const useUpdateAccountSettings = () => {
|
|
160
|
-
const { oxyServices, activeSessionId } = useWebOxy();
|
|
224
|
+
const { oxyServices, activeSessionId, user } = useWebOxy();
|
|
161
225
|
const queryClient = useQueryClient();
|
|
162
226
|
|
|
163
|
-
|
|
164
|
-
mutationFn: async (
|
|
165
|
-
|
|
227
|
+
const mutation = useMutation({
|
|
228
|
+
mutationFn: async ({ updates, currentUser }: UpdateAccountSettingsVariables) => {
|
|
229
|
+
const userId = currentUser.id;
|
|
230
|
+
if (!userId) {
|
|
231
|
+
throw new Error('User ID is required to update account settings');
|
|
232
|
+
}
|
|
233
|
+
const updatedPrivacy = await authenticatedApiCall<PrivacySettings>(
|
|
234
|
+
oxyServices,
|
|
235
|
+
activeSessionId,
|
|
236
|
+
() => oxyServices.updatePrivacySettings(updates, userId)
|
|
237
|
+
);
|
|
238
|
+
// Rebuild against the dispatch-time snapshot, NOT the live cache.
|
|
239
|
+
// The cache may have been mutated by a sibling write between
|
|
240
|
+
// dispatch and settle.
|
|
241
|
+
return {
|
|
242
|
+
...currentUser,
|
|
243
|
+
privacySettings: updatedPrivacy,
|
|
244
|
+
};
|
|
166
245
|
},
|
|
167
|
-
onMutate: async (
|
|
246
|
+
onMutate: async ({ updates }) => {
|
|
168
247
|
await queryClient.cancelQueries({ queryKey: queryKeys.accounts.settings() });
|
|
169
248
|
const previousUser = queryClient.getQueryData<User>(queryKeys.accounts.current());
|
|
170
249
|
|
|
@@ -173,25 +252,42 @@ export const useUpdateAccountSettings = () => {
|
|
|
173
252
|
...previousUser,
|
|
174
253
|
privacySettings: {
|
|
175
254
|
...previousUser.privacySettings,
|
|
176
|
-
...
|
|
255
|
+
...updates,
|
|
177
256
|
},
|
|
178
257
|
});
|
|
179
258
|
}
|
|
180
259
|
|
|
181
260
|
return { previousUser };
|
|
182
261
|
},
|
|
183
|
-
onError: (error,
|
|
184
|
-
|
|
185
|
-
|
|
262
|
+
onError: (error, { updates }, context) => {
|
|
263
|
+
// Restore only the privacySettings keys this mutation tried to change
|
|
264
|
+
if (context?.previousUser && updates) {
|
|
265
|
+
const previousPrivacy = context.previousUser.privacySettings ?? {};
|
|
266
|
+
const changedKeys = Object.keys(updates) as Array<keyof PrivacySettings>;
|
|
267
|
+
const partialPrivacyRollback = changedKeys.reduce<Partial<PrivacySettings>>((acc, key) => {
|
|
268
|
+
(acc as Record<string, unknown>)[key as string] = (previousPrivacy as Record<string, unknown>)[key as string];
|
|
269
|
+
return acc;
|
|
270
|
+
}, {});
|
|
271
|
+
|
|
272
|
+
const current = queryClient.getQueryData<User>(queryKeys.accounts.current());
|
|
273
|
+
if (current) {
|
|
274
|
+
queryClient.setQueryData<User>(queryKeys.accounts.current(), {
|
|
275
|
+
...current,
|
|
276
|
+
privacySettings: {
|
|
277
|
+
...(current.privacySettings ?? {}),
|
|
278
|
+
...partialPrivacyRollback,
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
}
|
|
186
282
|
}
|
|
187
283
|
toast.error(error instanceof Error ? error.message : 'Failed to update settings');
|
|
188
284
|
},
|
|
189
285
|
onSuccess: (data) => {
|
|
190
286
|
queryClient.setQueryData(queryKeys.accounts.current(), data);
|
|
191
|
-
|
|
287
|
+
|
|
192
288
|
// Update authStore so frontend components see the changes immediately
|
|
193
289
|
useAuthStore.getState().setUser(data);
|
|
194
|
-
|
|
290
|
+
|
|
195
291
|
invalidateAccountQueries(queryClient);
|
|
196
292
|
toast.success('Settings updated successfully');
|
|
197
293
|
},
|
|
@@ -199,6 +295,27 @@ export const useUpdateAccountSettings = () => {
|
|
|
199
295
|
queryClient.invalidateQueries({ queryKey: queryKeys.accounts.settings() });
|
|
200
296
|
},
|
|
201
297
|
});
|
|
298
|
+
|
|
299
|
+
// Wrap mutate/mutateAsync so call sites pass a plain settings object and
|
|
300
|
+
// the current user is captured at dispatch time.
|
|
301
|
+
return {
|
|
302
|
+
...mutation,
|
|
303
|
+
mutate: (updates: Partial<PrivacySettings>): void => {
|
|
304
|
+
const currentUser = user ?? queryClient.getQueryData<User>(queryKeys.accounts.current());
|
|
305
|
+
if (!currentUser) {
|
|
306
|
+
toast.error('Cannot update account settings: no current user');
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
mutation.mutate({ updates, currentUser });
|
|
310
|
+
},
|
|
311
|
+
mutateAsync: async (updates: Partial<PrivacySettings>): Promise<User> => {
|
|
312
|
+
const currentUser = user ?? queryClient.getQueryData<User>(queryKeys.accounts.current());
|
|
313
|
+
if (!currentUser) {
|
|
314
|
+
throw new Error('Cannot update account settings: no current user');
|
|
315
|
+
}
|
|
316
|
+
return mutation.mutateAsync({ updates, currentUser });
|
|
317
|
+
},
|
|
318
|
+
};
|
|
202
319
|
};
|
|
203
320
|
|
|
204
321
|
/**
|
|
@@ -209,13 +326,13 @@ export const useUpdatePrivacySettings = () => {
|
|
|
209
326
|
const queryClient = useQueryClient();
|
|
210
327
|
|
|
211
328
|
return useMutation({
|
|
212
|
-
mutationFn: async ({ settings, userId }: { settings:
|
|
329
|
+
mutationFn: async ({ settings, userId }: { settings: Partial<PrivacySettings>; userId?: string }) => {
|
|
213
330
|
const targetUserId = userId || user?.id;
|
|
214
331
|
if (!targetUserId) {
|
|
215
332
|
throw new Error('User ID is required');
|
|
216
333
|
}
|
|
217
334
|
|
|
218
|
-
return authenticatedApiCall<
|
|
335
|
+
return authenticatedApiCall<PrivacySettings>(
|
|
219
336
|
oxyServices,
|
|
220
337
|
activeSessionId,
|
|
221
338
|
() => oxyServices.updatePrivacySettings(settings, targetUserId)
|
|
@@ -231,12 +348,12 @@ export const useUpdatePrivacySettings = () => {
|
|
|
231
348
|
await queryClient.cancelQueries({ queryKey: queryKeys.accounts.current() });
|
|
232
349
|
|
|
233
350
|
// Snapshot previous values
|
|
234
|
-
const previousPrivacySettings = queryClient.getQueryData(queryKeys.privacy.settings(targetUserId));
|
|
351
|
+
const previousPrivacySettings = queryClient.getQueryData<PrivacySettings>(queryKeys.privacy.settings(targetUserId));
|
|
235
352
|
const previousUser = queryClient.getQueryData<User>(queryKeys.accounts.current());
|
|
236
353
|
|
|
237
354
|
// Optimistically update privacy settings
|
|
238
355
|
if (previousPrivacySettings) {
|
|
239
|
-
queryClient.setQueryData(queryKeys.privacy.settings(targetUserId), {
|
|
356
|
+
queryClient.setQueryData<PrivacySettings>(queryKeys.privacy.settings(targetUserId), {
|
|
240
357
|
...previousPrivacySettings,
|
|
241
358
|
...settings,
|
|
242
359
|
});
|
|
@@ -255,44 +372,100 @@ export const useUpdatePrivacySettings = () => {
|
|
|
255
372
|
|
|
256
373
|
return { previousPrivacySettings, previousUser };
|
|
257
374
|
},
|
|
258
|
-
// On error, rollback
|
|
259
|
-
|
|
375
|
+
// On error, rollback ONLY the privacy keys this mutation tried to change.
|
|
376
|
+
// Restoring the entire previous object would wipe out other concurrent
|
|
377
|
+
// optimistic updates (e.g. user toggles two privacy switches in quick
|
|
378
|
+
// succession; failure on one must not revert the other).
|
|
379
|
+
onError: (error, { settings, userId }, context) => {
|
|
260
380
|
const targetUserId = userId || user?.id;
|
|
261
|
-
|
|
262
|
-
|
|
381
|
+
const changedKeys = settings ? (Object.keys(settings) as Array<keyof PrivacySettings>) : [];
|
|
382
|
+
|
|
383
|
+
// Rollback the privacy.settings query (partial)
|
|
384
|
+
if (context?.previousPrivacySettings && targetUserId && changedKeys.length > 0) {
|
|
385
|
+
const previousPrivacy = context.previousPrivacySettings as Record<string, unknown>;
|
|
386
|
+
const partialPrivacyRollback = changedKeys.reduce<Partial<PrivacySettings>>((acc, key) => {
|
|
387
|
+
(acc as Record<string, unknown>)[key as string] = previousPrivacy[key as string];
|
|
388
|
+
return acc;
|
|
389
|
+
}, {});
|
|
390
|
+
const currentPrivacy = queryClient.getQueryData<PrivacySettings>(queryKeys.privacy.settings(targetUserId));
|
|
391
|
+
if (currentPrivacy) {
|
|
392
|
+
queryClient.setQueryData<PrivacySettings>(queryKeys.privacy.settings(targetUserId), {
|
|
393
|
+
...currentPrivacy,
|
|
394
|
+
...partialPrivacyRollback,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
263
397
|
}
|
|
264
|
-
|
|
265
|
-
|
|
398
|
+
|
|
399
|
+
// Rollback the accounts.current() user.privacySettings (partial)
|
|
400
|
+
if (context?.previousUser && changedKeys.length > 0) {
|
|
401
|
+
const previousPrivacy = (context.previousUser.privacySettings ?? {}) as Record<string, unknown>;
|
|
402
|
+
const partialPrivacyRollback = changedKeys.reduce<Partial<PrivacySettings>>((acc, key) => {
|
|
403
|
+
(acc as Record<string, unknown>)[key as string] = previousPrivacy[key as string];
|
|
404
|
+
return acc;
|
|
405
|
+
}, {});
|
|
406
|
+
const current = queryClient.getQueryData<User>(queryKeys.accounts.current());
|
|
407
|
+
if (current) {
|
|
408
|
+
queryClient.setQueryData<User>(queryKeys.accounts.current(), {
|
|
409
|
+
...current,
|
|
410
|
+
privacySettings: {
|
|
411
|
+
...(current.privacySettings ?? {}),
|
|
412
|
+
...partialPrivacyRollback,
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
}
|
|
266
416
|
}
|
|
417
|
+
|
|
418
|
+
// After partial rollback, reconcile against the server so the cache
|
|
419
|
+
// converges to the authoritative state for the failed keys.
|
|
420
|
+
if (targetUserId) {
|
|
421
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.privacy.settings(targetUserId) });
|
|
422
|
+
}
|
|
423
|
+
|
|
267
424
|
toast.error(error instanceof Error ? error.message : 'Failed to update privacy settings');
|
|
268
425
|
},
|
|
269
|
-
// On success,
|
|
270
|
-
|
|
426
|
+
// On success, MERGE the server response into the cached state. Older
|
|
427
|
+
// API builds returned only the changed field (or wiped the privacySettings
|
|
428
|
+
// subdocument when handed a partial update), which would clobber every
|
|
429
|
+
// other toggle if we blindly replaced. Defensive merge means the UI stays
|
|
430
|
+
// consistent regardless of server behaviour.
|
|
431
|
+
//
|
|
432
|
+
// BOTH the privacy.settings query AND the accounts.current() user are
|
|
433
|
+
// gated on `targetUserId`. If it's missing (no userId param, no logged-in
|
|
434
|
+
// user) the optimistic update in onMutate would have early-returned too,
|
|
435
|
+
// so neither cache was ever touched — there's nothing to reconcile here.
|
|
436
|
+
onSuccess: (data, { userId, settings }) => {
|
|
271
437
|
const targetUserId = userId || user?.id;
|
|
272
|
-
if (targetUserId)
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
438
|
+
if (!targetUserId) return;
|
|
439
|
+
|
|
440
|
+
const incoming = (data ?? {}) as PrivacySettings;
|
|
441
|
+
const requested = (settings ?? {}) as Partial<PrivacySettings>;
|
|
442
|
+
|
|
443
|
+
queryClient.setQueryData<PrivacySettings>(
|
|
444
|
+
queryKeys.privacy.settings(targetUserId),
|
|
445
|
+
(previous) => ({
|
|
446
|
+
...(previous ?? {}),
|
|
447
|
+
...requested,
|
|
448
|
+
...incoming, // server wins for fields it explicitly returned
|
|
449
|
+
}),
|
|
450
|
+
);
|
|
451
|
+
|
|
276
452
|
const currentUser = queryClient.getQueryData<User>(queryKeys.accounts.current());
|
|
277
453
|
if (currentUser) {
|
|
278
454
|
const updatedUser: User = {
|
|
279
455
|
...currentUser,
|
|
280
|
-
privacySettings:
|
|
456
|
+
privacySettings: {
|
|
457
|
+
...(currentUser.privacySettings ?? {}),
|
|
458
|
+
...requested,
|
|
459
|
+
...incoming,
|
|
460
|
+
},
|
|
281
461
|
};
|
|
282
462
|
queryClient.setQueryData<User>(queryKeys.accounts.current(), updatedUser);
|
|
283
|
-
|
|
284
|
-
// Update authStore so frontend components see the changes immediately
|
|
285
463
|
useAuthStore.getState().setUser(updatedUser);
|
|
286
464
|
}
|
|
287
|
-
invalidateAccountQueries
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const targetUserId = userId || user?.id;
|
|
292
|
-
if (targetUserId) {
|
|
293
|
-
queryClient.invalidateQueries({ queryKey: queryKeys.privacy.settings(targetUserId) });
|
|
294
|
-
}
|
|
295
|
-
queryClient.invalidateQueries({ queryKey: queryKeys.accounts.current() });
|
|
465
|
+
// Deliberately NOT invalidating any queries here. invalidateAccountQueries
|
|
466
|
+
// invalidates accounts.all which is the prefix for accounts.current(),
|
|
467
|
+
// triggering a background refetch of useCurrentUser that would overwrite
|
|
468
|
+
// the merged state above. The onSuccess merge is the source of truth.
|
|
296
469
|
},
|
|
297
470
|
});
|
|
298
471
|
};
|
|
@@ -331,7 +504,7 @@ export const useUploadFile = () => {
|
|
|
331
504
|
}: {
|
|
332
505
|
file: File;
|
|
333
506
|
visibility?: 'private' | 'public' | 'unlisted';
|
|
334
|
-
metadata?: Record<string,
|
|
507
|
+
metadata?: Record<string, unknown>;
|
|
335
508
|
onProgress?: (progress: number) => void;
|
|
336
509
|
}) => {
|
|
337
510
|
return authenticatedApiCall<UploadResult>(
|