@oxyhq/services 5.17.13 → 5.17.15
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/lib/commonjs/index.js +30 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +23 -27
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +36 -13
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +36 -22
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +9 -3
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/stores/accountStore.js +4 -2
- package/lib/commonjs/ui/stores/accountStore.js.map +1 -1
- package/lib/commonjs/ui/utils/avatarUtils.js +34 -3
- package/lib/commonjs/ui/utils/avatarUtils.js.map +1 -1
- package/lib/module/index.js +3 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +29 -33
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/mutations/useAccountMutations.js +37 -14
- package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/module/ui/hooks/queries/useAccountQueries.js +36 -22
- package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +9 -3
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/stores/accountStore.js +4 -2
- package/lib/module/ui/stores/accountStore.js.map +1 -1
- package/lib/module/ui/utils/avatarUtils.js +33 -3
- package/lib/module/ui/utils/avatarUtils.js.map +1 -1
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/stores/accountStore.d.ts.map +1 -1
- package/lib/typescript/ui/utils/avatarUtils.d.ts +10 -0
- package/lib/typescript/ui/utils/avatarUtils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +9 -1
- package/src/ui/context/OxyContext.tsx +25 -32
- package/src/ui/hooks/mutations/useAccountMutations.ts +38 -16
- package/src/ui/hooks/queries/useAccountQueries.ts +27 -15
- package/src/ui/screens/AccountSettingsScreen.tsx +17 -3
- package/src/ui/stores/accountStore.ts +11 -1
- package/src/ui/utils/avatarUtils.ts +40 -3
|
@@ -12,6 +12,7 @@ import { QueryClient } from '@tanstack/react-query';
|
|
|
12
12
|
*/
|
|
13
13
|
export declare function updateAvatarVisibility(fileId: string | undefined, oxyServices: OxyServices, contextName?: string): Promise<void>;
|
|
14
14
|
/**
|
|
15
|
+
* @deprecated Use refreshAccountInStore instead for full profile sync
|
|
15
16
|
* Refreshes avatar in accountStore with cache-busted URL to force image reload.
|
|
16
17
|
*
|
|
17
18
|
* @param sessionId - The session ID for the account to update
|
|
@@ -19,6 +20,15 @@ export declare function updateAvatarVisibility(fileId: string | undefined, oxySe
|
|
|
19
20
|
* @param oxyServices - OxyServices instance to generate download URL
|
|
20
21
|
*/
|
|
21
22
|
export declare function refreshAvatarInStore(sessionId: string, avatarFileId: string, oxyServices: OxyServices): void;
|
|
23
|
+
/**
|
|
24
|
+
* Refreshes all user profile data in accountStore (username, displayName, avatar).
|
|
25
|
+
* This ensures accountStore stays in sync with profile changes.
|
|
26
|
+
*
|
|
27
|
+
* @param sessionId - The session ID for the account to update
|
|
28
|
+
* @param userData - The updated user data
|
|
29
|
+
* @param oxyServices - OxyServices instance to generate download URL
|
|
30
|
+
*/
|
|
31
|
+
export declare function refreshAccountInStore(sessionId: string, userData: Partial<User>, oxyServices: OxyServices): void;
|
|
22
32
|
/**
|
|
23
33
|
* Updates user profile with avatar and handles all side effects (query invalidation, accountStore update).
|
|
24
34
|
* This function can be used from within OxyContext provider without requiring useOxy hook.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"avatarUtils.d.ts","sourceRoot":"","sources":["../../../../src/ui/utils/avatarUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"avatarUtils.d.ts","sourceRoot":"","sources":["../../../../src/ui/utils/avatarUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIpD;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,WAAW,EAAE,WAAW,EACxB,WAAW,GAAE,MAAsB,GAClC,OAAO,CAAC,IAAI,CAAC,CAcf;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,WAAW,GACvB,IAAI,CAON;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,EACvB,WAAW,EAAE,WAAW,GACvB,IAAI,CAqBN;AAED;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,EACtB,WAAW,EAAE,WAAW,EACxB,eAAe,EAAE,MAAM,GAAG,IAAI,EAC9B,WAAW,EAAE,WAAW,EACxB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9B,OAAO,CAAC,IAAI,CAAC,CAyCf"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/services",
|
|
3
|
-
"version": "5.17.
|
|
3
|
+
"version": "5.17.15",
|
|
4
4
|
"description": "Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀",
|
|
5
5
|
"main": "lib/commonjs/index.js",
|
|
6
6
|
"module": "lib/module/index.js",
|
package/src/index.ts
CHANGED
|
@@ -193,4 +193,12 @@ export {
|
|
|
193
193
|
logPerformance
|
|
194
194
|
} from './utils/loggerUtils';
|
|
195
195
|
export * from './utils/asyncUtils';
|
|
196
|
-
export * from './utils/hookUtils';
|
|
196
|
+
export * from './utils/hookUtils';
|
|
197
|
+
|
|
198
|
+
// Avatar and account utilities
|
|
199
|
+
export {
|
|
200
|
+
refreshAvatarInStore,
|
|
201
|
+
refreshAccountInStore,
|
|
202
|
+
updateAvatarVisibility,
|
|
203
|
+
updateProfileWithAvatar
|
|
204
|
+
} from './ui/utils/avatarUtils';
|
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
useRef,
|
|
7
7
|
useState,
|
|
8
8
|
} from 'react';
|
|
9
|
-
import { useQueryClient, useQuery
|
|
9
|
+
import { useQueryClient, useQuery } from '@tanstack/react-query';
|
|
10
10
|
import { toast } from '../../lib/sonner';
|
|
11
11
|
import { OxyServices } from '../../core';
|
|
12
12
|
import type { User } from '../../models/interfaces';
|
|
13
13
|
import type { ClientSession } from '../../models/session';
|
|
14
|
-
import { logger as
|
|
14
|
+
import { logger as appLogger } from '../../utils/loggerUtils';
|
|
15
15
|
import { useAuthOperations } from './hooks/useAuthOperations';
|
|
16
16
|
import { useAvatarPicker } from '../hooks/useAvatarPicker';
|
|
17
17
|
import { useSessionSocket } from '../hooks/useSessionSocket';
|
|
@@ -22,13 +22,13 @@ import { useSessionManagement } from '../hooks/useSessionManagement';
|
|
|
22
22
|
import { useStorage } from '../hooks/useStorage';
|
|
23
23
|
import { useAccountStore } from '../stores/accountStore';
|
|
24
24
|
import { useAuthStore, type AuthState } from '../stores/authStore';
|
|
25
|
-
import { queryKeys
|
|
25
|
+
import { queryKeys } from '../hooks/queries/queryKeys';
|
|
26
26
|
import { clearQueryCache } from '../hooks/queryClient';
|
|
27
27
|
import type { RouteName } from '../navigation/routes';
|
|
28
28
|
import { showBottomSheet as globalShowBottomSheet } from '../navigation/bottomSheetManager';
|
|
29
29
|
import { getStorageKeys } from '../utils/storageHelpers';
|
|
30
30
|
import { isInvalidSessionError, isTimeoutOrNetworkError } from '../utils/errorHandlers';
|
|
31
|
-
import {
|
|
31
|
+
import { useUpdateProfile as useUpdateProfileMutation } from '../hooks/mutations/useAccountMutations';
|
|
32
32
|
import { useShallow } from 'zustand/react/shallow';
|
|
33
33
|
|
|
34
34
|
import {
|
|
@@ -55,7 +55,7 @@ const loadUseFollowHook = (): UseFollowHook => {
|
|
|
55
55
|
return cachedUseFollowHook;
|
|
56
56
|
} catch (error) {
|
|
57
57
|
if (__DEV__) {
|
|
58
|
-
|
|
58
|
+
appLogger.warn(
|
|
59
59
|
'useFollow hook is not available. Please import useFollow from @oxyhq/services directly.',
|
|
60
60
|
{ component: 'OxyContext', method: 'loadUseFollowHook' },
|
|
61
61
|
error
|
|
@@ -174,41 +174,34 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
174
174
|
queryClient,
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
-
const { data: userData } = useQuery({
|
|
177
|
+
const { data: userData, refetch: refetchUser } = useQuery({
|
|
178
178
|
queryKey: queryKeys.accounts.current(),
|
|
179
179
|
queryFn: async () => {
|
|
180
|
+
appLogger.debug('Fetching current user', {
|
|
181
|
+
component: 'OxyContext',
|
|
182
|
+
sessionId: activeSessionId ?? undefined
|
|
183
|
+
});
|
|
180
184
|
if (!activeSessionId) {
|
|
181
185
|
throw new Error('No active session');
|
|
182
186
|
}
|
|
183
|
-
|
|
187
|
+
const data = await oxyServices.getUserBySession(activeSessionId);
|
|
188
|
+
appLogger.debug('Current user fetched', {
|
|
189
|
+
component: 'OxyContext',
|
|
190
|
+
username: data.username
|
|
191
|
+
});
|
|
192
|
+
return data;
|
|
184
193
|
},
|
|
185
194
|
enabled: isAuthenticated && !!activeSessionId && tokenReady,
|
|
186
|
-
staleTime:
|
|
187
|
-
gcTime:
|
|
195
|
+
staleTime: 30000, // 30 seconds - reduces unnecessary refetches
|
|
196
|
+
gcTime: 5 * 60 * 1000, // 5 minutes - keeps data in cache longer
|
|
188
197
|
retry: false,
|
|
198
|
+
refetchOnMount: 'always',
|
|
199
|
+
refetchOnWindowFocus: false,
|
|
189
200
|
});
|
|
190
201
|
const user = userData ?? null;
|
|
191
202
|
|
|
192
|
-
//
|
|
193
|
-
const updateProfileMutation =
|
|
194
|
-
mutationFn: async (updates: Partial<User>) => {
|
|
195
|
-
return await oxyServices.updateProfile(updates);
|
|
196
|
-
},
|
|
197
|
-
onSuccess: (data, updates) => {
|
|
198
|
-
// Update cache with server response
|
|
199
|
-
queryClient.setQueryData(queryKeys.accounts.current(), data);
|
|
200
|
-
if (activeSessionId) {
|
|
201
|
-
queryClient.setQueryData(queryKeys.users.profile(activeSessionId), data);
|
|
202
|
-
}
|
|
203
|
-
// If avatar was updated, refresh accountStore with cache-busted URL
|
|
204
|
-
if (updates.avatar && activeSessionId && oxyServices) {
|
|
205
|
-
refreshAvatarInStore(activeSessionId, updates.avatar, oxyServices);
|
|
206
|
-
}
|
|
207
|
-
// Invalidate all related queries to refresh everywhere
|
|
208
|
-
invalidateUserQueries(queryClient);
|
|
209
|
-
invalidateAccountQueries(queryClient);
|
|
210
|
-
},
|
|
211
|
-
});
|
|
203
|
+
// Use the shared profile update mutation hook
|
|
204
|
+
const updateProfileMutation = useUpdateProfileMutation();
|
|
212
205
|
|
|
213
206
|
const updateProfile = useCallback(
|
|
214
207
|
async (updates: Partial<User>): Promise<User> => {
|
|
@@ -360,7 +353,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
360
353
|
logger('Session validation failed during init', validationError);
|
|
361
354
|
} else if (__DEV__ && isTimeoutOrNetworkError(validationError)) {
|
|
362
355
|
// Only log timeouts in dev mode for debugging
|
|
363
|
-
|
|
356
|
+
appLogger.debug('Session validation timeout (expected when offline)', { component: 'OxyContext', method: 'restoreSessionsFromStorage' }, validationError as unknown);
|
|
364
357
|
}
|
|
365
358
|
}
|
|
366
359
|
}
|
|
@@ -385,7 +378,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
385
378
|
} else if (isTimeoutOrNetworkError(switchError)) {
|
|
386
379
|
// Timeout/network error - non-critical, don't block
|
|
387
380
|
if (__DEV__) {
|
|
388
|
-
|
|
381
|
+
appLogger.debug('Active session validation timeout (expected when offline)', { component: 'OxyContext', method: 'restoreSessionsFromStorage' }, switchError as unknown);
|
|
389
382
|
}
|
|
390
383
|
} else {
|
|
391
384
|
// Only log unexpected errors
|
|
@@ -395,7 +388,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
395
388
|
}
|
|
396
389
|
} catch (error) {
|
|
397
390
|
if (__DEV__) {
|
|
398
|
-
|
|
391
|
+
appLogger.error('Auth init error', error, { component: 'OxyContext', method: 'restoreSessionsFromStorage' });
|
|
399
392
|
}
|
|
400
393
|
await clearSessionState();
|
|
401
394
|
} finally {
|
|
@@ -4,7 +4,8 @@ import type { ClientSession } from '../../../models/session';
|
|
|
4
4
|
import { queryKeys, invalidateAccountQueries, invalidateUserQueries } from '../queries/queryKeys';
|
|
5
5
|
import { useOxy } from '../../context/OxyContext';
|
|
6
6
|
import { toast } from '../../../lib/sonner';
|
|
7
|
-
import {
|
|
7
|
+
import { refreshAccountInStore } from '../../utils/avatarUtils';
|
|
8
|
+
import { logger } from '../../../utils/loggerUtils';
|
|
8
9
|
|
|
9
10
|
const getDeviceIdForSession = (sessions: ClientSession[] = [], sessionId: string | null) =>
|
|
10
11
|
sessionId ? sessions.find((s) => s.sessionId === sessionId)?.deviceId : undefined;
|
|
@@ -79,22 +80,29 @@ export const useUpdateProfile = () => {
|
|
|
79
80
|
}
|
|
80
81
|
toast.error(error instanceof Error ? error.message : 'Failed to update profile');
|
|
81
82
|
},
|
|
82
|
-
// On success,
|
|
83
|
-
onSuccess: (data, updates) => {
|
|
83
|
+
// On success, update cache and sync to store
|
|
84
|
+
onSuccess: async (data, updates) => {
|
|
85
|
+
logger.debug('Profile update successful', {
|
|
86
|
+
component: 'useUpdateProfile',
|
|
87
|
+
username: data.username,
|
|
88
|
+
updatedFields: Object.keys(updates)
|
|
89
|
+
});
|
|
90
|
+
|
|
84
91
|
// Update cache with server response
|
|
85
92
|
queryClient.setQueryData(queryKeys.accounts.current(), data);
|
|
86
93
|
if (activeSessionId) {
|
|
87
94
|
queryClient.setQueryData(queryKeys.users.profile(activeSessionId), data);
|
|
88
95
|
}
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
if (
|
|
92
|
-
|
|
96
|
+
|
|
97
|
+
// Refresh accountStore with all updated profile data (synchronizes immediately)
|
|
98
|
+
if (activeSessionId && oxyServices) {
|
|
99
|
+
refreshAccountInStore(activeSessionId, data, oxyServices);
|
|
93
100
|
}
|
|
94
|
-
|
|
95
|
-
//
|
|
96
|
-
invalidateUserQueries(queryClient);
|
|
101
|
+
|
|
102
|
+
// Only invalidate specific queries that need refresh (not everything)
|
|
97
103
|
invalidateAccountQueries(queryClient);
|
|
104
|
+
|
|
105
|
+
logger.debug('Profile update complete', { component: 'useUpdateProfile' });
|
|
98
106
|
},
|
|
99
107
|
});
|
|
100
108
|
};
|
|
@@ -168,21 +176,35 @@ export const useUploadAvatar = () => {
|
|
|
168
176
|
}
|
|
169
177
|
toast.error(error instanceof Error ? error.message : 'Failed to upload avatar');
|
|
170
178
|
},
|
|
171
|
-
onSuccess: (data) => {
|
|
179
|
+
onSuccess: async (data) => {
|
|
180
|
+
logger.debug(
|
|
181
|
+
'Avatar upload successful',
|
|
182
|
+
{ component: 'useUploadAvatar', method: 'onSuccess', data }
|
|
183
|
+
);
|
|
184
|
+
|
|
172
185
|
queryClient.setQueryData(queryKeys.accounts.current(), data);
|
|
173
186
|
if (activeSessionId) {
|
|
174
187
|
queryClient.setQueryData(queryKeys.users.profile(activeSessionId), data);
|
|
175
188
|
}
|
|
176
189
|
|
|
177
|
-
// Refresh accountStore with
|
|
178
|
-
if (
|
|
179
|
-
|
|
190
|
+
// Refresh accountStore with all updated profile data (including avatar)
|
|
191
|
+
if (activeSessionId && oxyServices) {
|
|
192
|
+
refreshAccountInStore(activeSessionId, data, oxyServices);
|
|
180
193
|
}
|
|
181
194
|
|
|
182
195
|
// Invalidate all related queries to refresh everywhere
|
|
183
|
-
invalidateUserQueries(queryClient);
|
|
184
|
-
invalidateAccountQueries(queryClient);
|
|
196
|
+
await invalidateUserQueries(queryClient);
|
|
197
|
+
await invalidateAccountQueries(queryClient);
|
|
198
|
+
|
|
199
|
+
// Explicitly refetch to ensure UI updates
|
|
200
|
+
await queryClient.refetchQueries({ queryKey: queryKeys.accounts.current() });
|
|
201
|
+
|
|
185
202
|
toast.success('Avatar updated successfully');
|
|
203
|
+
|
|
204
|
+
logger.debug(
|
|
205
|
+
'Avatar update complete - cache invalidated and refetched',
|
|
206
|
+
{ component: 'useUploadAvatar', method: 'onSuccess' }
|
|
207
|
+
);
|
|
186
208
|
},
|
|
187
209
|
});
|
|
188
210
|
};
|
|
@@ -3,6 +3,7 @@ import type { User } from '../../../models/interfaces';
|
|
|
3
3
|
import type { OxyServices } from '../../../core';
|
|
4
4
|
import { queryKeys } from './queryKeys';
|
|
5
5
|
import { useOxy } from '../../context/OxyContextBase';
|
|
6
|
+
import { logger } from '../../../utils/loggerUtils';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Get user profile by session ID
|
|
@@ -19,8 +20,8 @@ export const useUserProfile = (sessionId: string | null, options?: { enabled?: b
|
|
|
19
20
|
return await oxyServices.getUserBySession(sessionId);
|
|
20
21
|
},
|
|
21
22
|
enabled: (options?.enabled !== false) && !!sessionId,
|
|
22
|
-
staleTime:
|
|
23
|
-
gcTime:
|
|
23
|
+
staleTime: 30000, // 30 seconds - reasonable freshness without constant refetching
|
|
24
|
+
gcTime: 5 * 60 * 1000, // 5 minutes - keep in cache for quick access
|
|
24
25
|
});
|
|
25
26
|
};
|
|
26
27
|
|
|
@@ -38,8 +39,8 @@ export const useUserProfiles = (sessionIds: string[], options?: { enabled?: bool
|
|
|
38
39
|
return results[0]?.user || null;
|
|
39
40
|
},
|
|
40
41
|
enabled: (options?.enabled !== false) && !!sessionId,
|
|
41
|
-
staleTime:
|
|
42
|
-
gcTime:
|
|
42
|
+
staleTime: 30000, // 30 seconds
|
|
43
|
+
gcTime: 5 * 60 * 1000, // 5 minutes
|
|
43
44
|
})),
|
|
44
45
|
});
|
|
45
46
|
};
|
|
@@ -53,14 +54,25 @@ export const useCurrentUser = (options?: { enabled?: boolean }) => {
|
|
|
53
54
|
return useQuery({
|
|
54
55
|
queryKey: queryKeys.accounts.current(),
|
|
55
56
|
queryFn: async () => {
|
|
57
|
+
logger.debug('Fetching current user', {
|
|
58
|
+
component: 'useCurrentUser',
|
|
59
|
+
sessionId: activeSessionId ?? undefined
|
|
60
|
+
});
|
|
56
61
|
if (!activeSessionId) {
|
|
57
62
|
throw new Error('No active session');
|
|
58
63
|
}
|
|
59
|
-
|
|
64
|
+
const userData = await oxyServices.getUserBySession(activeSessionId);
|
|
65
|
+
logger.debug('Current user fetched', {
|
|
66
|
+
component: 'useCurrentUser',
|
|
67
|
+
username: userData.username
|
|
68
|
+
});
|
|
69
|
+
return userData;
|
|
60
70
|
},
|
|
61
71
|
enabled: (options?.enabled !== false) && isAuthenticated && !!activeSessionId,
|
|
62
|
-
staleTime:
|
|
63
|
-
gcTime:
|
|
72
|
+
staleTime: 30000, // 30 seconds - balances freshness with performance
|
|
73
|
+
gcTime: 5 * 60 * 1000, // 5 minutes
|
|
74
|
+
refetchOnMount: 'always', // Always refetch on mount
|
|
75
|
+
refetchOnWindowFocus: false,
|
|
64
76
|
});
|
|
65
77
|
};
|
|
66
78
|
|
|
@@ -79,8 +91,8 @@ export const useUserById = (userId: string | null, options?: { enabled?: boolean
|
|
|
79
91
|
return await oxyServices.getUserById(userId);
|
|
80
92
|
},
|
|
81
93
|
enabled: (options?.enabled !== false) && !!userId,
|
|
82
|
-
staleTime:
|
|
83
|
-
gcTime:
|
|
94
|
+
staleTime: 60000, // 1 minute - other users' profiles change less frequently
|
|
95
|
+
gcTime: 10 * 60 * 1000, // 10 minutes
|
|
84
96
|
});
|
|
85
97
|
};
|
|
86
98
|
|
|
@@ -99,8 +111,8 @@ export const useUserByUsername = (username: string | null, options?: { enabled?:
|
|
|
99
111
|
return await oxyServices.getProfileByUsername(username);
|
|
100
112
|
},
|
|
101
113
|
enabled: (options?.enabled !== false) && !!username,
|
|
102
|
-
staleTime:
|
|
103
|
-
gcTime:
|
|
114
|
+
staleTime: 60000, // 1 minute
|
|
115
|
+
gcTime: 10 * 60 * 1000, // 10 minutes
|
|
104
116
|
});
|
|
105
117
|
};
|
|
106
118
|
|
|
@@ -119,8 +131,8 @@ export const useUsersBySessions = (sessionIds: string[], options?: { enabled?: b
|
|
|
119
131
|
return await oxyServices.getUsersBySessions(sessionIds);
|
|
120
132
|
},
|
|
121
133
|
enabled: (options?.enabled !== false) && sessionIds.length > 0,
|
|
122
|
-
staleTime:
|
|
123
|
-
gcTime:
|
|
134
|
+
staleTime: 30000, // 30 seconds
|
|
135
|
+
gcTime: 5 * 60 * 1000, // 5 minutes
|
|
124
136
|
});
|
|
125
137
|
};
|
|
126
138
|
|
|
@@ -167,7 +179,7 @@ export const usePrivacySettings = (userId?: string, options?: { enabled?: boolea
|
|
|
167
179
|
}
|
|
168
180
|
},
|
|
169
181
|
enabled: (options?.enabled !== false) && !!targetUserId,
|
|
170
|
-
staleTime:
|
|
171
|
-
gcTime:
|
|
182
|
+
staleTime: 60000, // 1 minute - privacy settings don't change frequently
|
|
183
|
+
gcTime: 10 * 60 * 1000, // 10 minutes
|
|
172
184
|
});
|
|
173
185
|
};
|
|
@@ -215,9 +215,10 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
|
|
|
215
215
|
// Only reset all fields if it's a new user or first load
|
|
216
216
|
// Skip reset if it's just an avatar update
|
|
217
217
|
if (shouldInitialize && !isAvatarOnlyUpdate) {
|
|
218
|
+
// Prioritize name.full over name.first for display name
|
|
218
219
|
const userDisplayName = typeof finalUser.name === 'string'
|
|
219
220
|
? finalUser.name
|
|
220
|
-
: finalUser.name?.
|
|
221
|
+
: finalUser.name?.full || finalUser.name?.first || '';
|
|
221
222
|
const userLastName = typeof finalUser.name === 'object' ? finalUser.name?.last || '' : '';
|
|
222
223
|
setDisplayName(userDisplayName);
|
|
223
224
|
setLastName(userLastName);
|
|
@@ -362,7 +363,15 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
|
|
|
362
363
|
try {
|
|
363
364
|
switch (field) {
|
|
364
365
|
case 'displayName':
|
|
365
|
-
|
|
366
|
+
// Send both first/last AND full name for proper synchronization
|
|
367
|
+
const fullName = [tempDisplayName, tempLastName].filter(Boolean).join(' ').trim() || tempDisplayName;
|
|
368
|
+
await updateProfileMutation.mutateAsync({
|
|
369
|
+
name: {
|
|
370
|
+
first: tempDisplayName,
|
|
371
|
+
last: tempLastName,
|
|
372
|
+
full: fullName
|
|
373
|
+
}
|
|
374
|
+
});
|
|
366
375
|
setDisplayName(tempDisplayName);
|
|
367
376
|
setLastName(tempLastName);
|
|
368
377
|
break;
|
|
@@ -467,7 +476,12 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
|
|
|
467
476
|
|
|
468
477
|
// Handle name field
|
|
469
478
|
if (displayName || lastName) {
|
|
470
|
-
|
|
479
|
+
const fullName = [displayName, lastName].filter(Boolean).join(' ').trim() || displayName;
|
|
480
|
+
updates.name = {
|
|
481
|
+
first: displayName,
|
|
482
|
+
last: lastName,
|
|
483
|
+
full: fullName
|
|
484
|
+
};
|
|
471
485
|
}
|
|
472
486
|
|
|
473
487
|
// Handle avatar
|
|
@@ -112,6 +112,8 @@ export const useAccountStore = create<AccountState>((set, get) => ({
|
|
|
112
112
|
return existing &&
|
|
113
113
|
existing.sessionId === newAccount.sessionId &&
|
|
114
114
|
existing.userId === newAccount.userId &&
|
|
115
|
+
existing.username === newAccount.username &&
|
|
116
|
+
existing.displayName === newAccount.displayName &&
|
|
115
117
|
existing.avatar === newAccount.avatar &&
|
|
116
118
|
existing.avatarUrl === newAccount.avatarUrl;
|
|
117
119
|
});
|
|
@@ -150,7 +152,15 @@ export const useAccountStore = create<AccountState>((set, get) => ({
|
|
|
150
152
|
if (!existing) return {} as any;
|
|
151
153
|
|
|
152
154
|
const updated = { ...existing, ...updates };
|
|
153
|
-
|
|
155
|
+
// Check if any meaningful field has changed
|
|
156
|
+
const hasChanges =
|
|
157
|
+
existing.username !== updated.username ||
|
|
158
|
+
existing.displayName !== updated.displayName ||
|
|
159
|
+
existing.avatar !== updated.avatar ||
|
|
160
|
+
existing.avatarUrl !== updated.avatarUrl ||
|
|
161
|
+
existing.userId !== updated.userId;
|
|
162
|
+
|
|
163
|
+
if (!hasChanges) {
|
|
154
164
|
return {} as any; // No change
|
|
155
165
|
}
|
|
156
166
|
|
|
@@ -3,6 +3,7 @@ import type { User } from '../../models/interfaces';
|
|
|
3
3
|
import { useAccountStore } from '../stores/accountStore';
|
|
4
4
|
import { QueryClient } from '@tanstack/react-query';
|
|
5
5
|
import { queryKeys, invalidateUserQueries, invalidateAccountQueries } from '../hooks/queries/queryKeys';
|
|
6
|
+
import { logger } from '../../utils/loggerUtils';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Updates file visibility to public for avatar use.
|
|
@@ -34,6 +35,7 @@ export async function updateAvatarVisibility(
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
/**
|
|
38
|
+
* @deprecated Use refreshAccountInStore instead for full profile sync
|
|
37
39
|
* Refreshes avatar in accountStore with cache-busted URL to force image reload.
|
|
38
40
|
*
|
|
39
41
|
* @param sessionId - The session ID for the account to update
|
|
@@ -53,6 +55,41 @@ export function refreshAvatarInStore(
|
|
|
53
55
|
});
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Refreshes all user profile data in accountStore (username, displayName, avatar).
|
|
60
|
+
* This ensures accountStore stays in sync with profile changes.
|
|
61
|
+
*
|
|
62
|
+
* @param sessionId - The session ID for the account to update
|
|
63
|
+
* @param userData - The updated user data
|
|
64
|
+
* @param oxyServices - OxyServices instance to generate download URL
|
|
65
|
+
*/
|
|
66
|
+
export function refreshAccountInStore(
|
|
67
|
+
sessionId: string,
|
|
68
|
+
userData: Partial<User>,
|
|
69
|
+
oxyServices: OxyServices
|
|
70
|
+
): void {
|
|
71
|
+
const { updateAccount } = useAccountStore.getState();
|
|
72
|
+
|
|
73
|
+
const displayName = userData.name?.full || userData.name?.first || userData.username || 'Account';
|
|
74
|
+
const avatarUrl = userData.avatar
|
|
75
|
+
? oxyServices.getFileDownloadUrl(userData.avatar, 'thumb') + `?t=${Date.now()}`
|
|
76
|
+
: undefined;
|
|
77
|
+
|
|
78
|
+
logger.debug('Refreshing account in store', {
|
|
79
|
+
component: 'AccountStore',
|
|
80
|
+
sessionId,
|
|
81
|
+
username: userData.username,
|
|
82
|
+
hasAvatar: !!userData.avatar
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
updateAccount(sessionId, {
|
|
86
|
+
username: userData.username,
|
|
87
|
+
displayName,
|
|
88
|
+
avatar: userData.avatar,
|
|
89
|
+
avatarUrl,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
56
93
|
/**
|
|
57
94
|
* Updates user profile with avatar and handles all side effects (query invalidation, accountStore update).
|
|
58
95
|
* This function can be used from within OxyContext provider without requiring useOxy hook.
|
|
@@ -89,9 +126,9 @@ export async function updateProfileWithAvatar(
|
|
|
89
126
|
queryClient.setQueryData(queryKeys.users.profile(activeSessionId), data);
|
|
90
127
|
}
|
|
91
128
|
|
|
92
|
-
//
|
|
93
|
-
if (
|
|
94
|
-
|
|
129
|
+
// Refresh accountStore with all updated profile data
|
|
130
|
+
if (activeSessionId) {
|
|
131
|
+
refreshAccountInStore(activeSessionId, data, oxyServices);
|
|
95
132
|
}
|
|
96
133
|
|
|
97
134
|
// Invalidate all related queries to refresh everywhere
|