@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.
Files changed (45) hide show
  1. package/lib/commonjs/index.js +30 -1
  2. package/lib/commonjs/index.js.map +1 -1
  3. package/lib/commonjs/ui/context/OxyContext.js +23 -27
  4. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  5. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +36 -13
  6. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  7. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +36 -22
  8. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
  9. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +9 -3
  10. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  11. package/lib/commonjs/ui/stores/accountStore.js +4 -2
  12. package/lib/commonjs/ui/stores/accountStore.js.map +1 -1
  13. package/lib/commonjs/ui/utils/avatarUtils.js +34 -3
  14. package/lib/commonjs/ui/utils/avatarUtils.js.map +1 -1
  15. package/lib/module/index.js +3 -0
  16. package/lib/module/index.js.map +1 -1
  17. package/lib/module/ui/context/OxyContext.js +29 -33
  18. package/lib/module/ui/context/OxyContext.js.map +1 -1
  19. package/lib/module/ui/hooks/mutations/useAccountMutations.js +37 -14
  20. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  21. package/lib/module/ui/hooks/queries/useAccountQueries.js +36 -22
  22. package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
  23. package/lib/module/ui/screens/AccountSettingsScreen.js +9 -3
  24. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  25. package/lib/module/ui/stores/accountStore.js +4 -2
  26. package/lib/module/ui/stores/accountStore.js.map +1 -1
  27. package/lib/module/ui/utils/avatarUtils.js +33 -3
  28. package/lib/module/ui/utils/avatarUtils.js.map +1 -1
  29. package/lib/typescript/index.d.ts +1 -0
  30. package/lib/typescript/index.d.ts.map +1 -1
  31. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  32. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  33. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  34. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  35. package/lib/typescript/ui/stores/accountStore.d.ts.map +1 -1
  36. package/lib/typescript/ui/utils/avatarUtils.d.ts +10 -0
  37. package/lib/typescript/ui/utils/avatarUtils.d.ts.map +1 -1
  38. package/package.json +1 -1
  39. package/src/index.ts +9 -1
  40. package/src/ui/context/OxyContext.tsx +25 -32
  41. package/src/ui/hooks/mutations/useAccountMutations.ts +38 -16
  42. package/src/ui/hooks/queries/useAccountQueries.ts +27 -15
  43. package/src/ui/screens/AccountSettingsScreen.tsx +17 -3
  44. package/src/ui/stores/accountStore.ts +11 -1
  45. 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;AAGpD;;;;;;;;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;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,WAAW,GACvB,IAAI,CAON;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"}
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.13",
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, useMutation } from '@tanstack/react-query';
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 loggerUtil } from '../../utils/loggerUtils';
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, invalidateAccountQueries, invalidateUserQueries } from '../hooks/queries/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 { refreshAvatarInStore } from '../utils/avatarUtils';
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
- loggerUtil.warn(
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
- return await oxyServices.getUserBySession(activeSessionId);
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: 0,
187
- gcTime: 0,
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
- // Update profile mutation - handles cache invalidation automatically
193
- const updateProfileMutation = useMutation({
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
- loggerUtil.debug('Session validation timeout (expected when offline)', { component: 'OxyContext', method: 'restoreSessionsFromStorage' }, validationError as unknown);
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
- loggerUtil.debug('Active session validation timeout (expected when offline)', { component: 'OxyContext', method: 'restoreSessionsFromStorage' }, switchError as unknown);
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
- loggerUtil.error('Auth init error', error instanceof Error ? error : new Error(String(error)), { component: 'OxyContext', method: 'restoreSessionsFromStorage' });
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 { refreshAvatarInStore } from '../../utils/avatarUtils';
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, invalidate and refetch
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
- // If avatar was updated, refresh accountStore with cache-busted URL
91
- if (updates.avatar && activeSessionId && oxyServices) {
92
- refreshAvatarInStore(activeSessionId, updates.avatar, oxyServices);
96
+
97
+ // Refresh accountStore with all updated profile data (synchronizes immediately)
98
+ if (activeSessionId && oxyServices) {
99
+ refreshAccountInStore(activeSessionId, data, oxyServices);
93
100
  }
94
-
95
- // Invalidate all related queries to refresh everywhere
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 cache-busted URL if avatar was updated
178
- if (data?.avatar && activeSessionId && oxyServices) {
179
- refreshAvatarInStore(activeSessionId, data.avatar, oxyServices);
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: 0, // Always fetch fresh - Services never caches profile
23
- gcTime: 0, // No garbage collection time - always fetch fresh
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: 0, // Always fetch fresh - Services never caches profile
42
- gcTime: 0, // No garbage collection time - always fetch fresh
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
- return await oxyServices.getUserBySession(activeSessionId);
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: 0, // Always fetch fresh - Services never caches profile
63
- gcTime: 0, // No garbage collection time - always fetch fresh
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: 0, // Always fetch fresh - Services never caches profile
83
- gcTime: 0, // No garbage collection time - always fetch fresh
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: 0, // Always fetch fresh - Services never caches profile
103
- gcTime: 0, // No garbage collection time - always fetch fresh
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: 0, // Always fetch fresh - Services never caches profile
123
- gcTime: 0, // No garbage collection time - always fetch fresh
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: 0, // Always fetch fresh - Services never caches profile
171
- gcTime: 0, // No garbage collection time - always fetch fresh
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?.first || finalUser.name?.full || '';
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
- await updateProfileMutation.mutateAsync({ name: { first: tempDisplayName, last: tempLastName } });
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
- updates.name = { first: displayName, last: lastName };
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
- if (existing.avatar === updated.avatar && existing.avatarUrl === updated.avatarUrl) {
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
- // If avatar was updated, refresh accountStore with cache-busted URL
93
- if (updates.avatar && activeSessionId) {
94
- refreshAvatarInStore(activeSessionId, updates.avatar, oxyServices);
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