@oxyhq/services 5.16.1 → 5.16.3

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 (175) hide show
  1. package/lib/commonjs/core/mixins/OxyServices.user.js +14 -13
  2. package/lib/commonjs/core/mixins/OxyServices.user.js.map +1 -1
  3. package/lib/commonjs/crypto/keyManager.js +164 -3
  4. package/lib/commonjs/crypto/keyManager.js.map +1 -1
  5. package/lib/commonjs/crypto/signatureService.js +26 -0
  6. package/lib/commonjs/crypto/signatureService.js.map +1 -1
  7. package/lib/commonjs/ui/components/GroupedSection.js +1 -1
  8. package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
  9. package/lib/commonjs/ui/components/OxyProvider.js +71 -24
  10. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  11. package/lib/commonjs/ui/components/profile/EditDisplayNameModal.js +1 -4
  12. package/lib/commonjs/ui/components/profile/EditDisplayNameModal.js.map +1 -1
  13. package/lib/commonjs/ui/context/OxyContext.js +175 -4
  14. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  15. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +148 -49
  16. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  17. package/lib/commonjs/ui/context/hooks/useSessionManagement.js +22 -2
  18. package/lib/commonjs/ui/context/hooks/useSessionManagement.js.map +1 -1
  19. package/lib/commonjs/ui/hooks/mutations/index.js +28 -0
  20. package/lib/commonjs/ui/hooks/mutations/index.js.map +1 -0
  21. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +314 -0
  22. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -0
  23. package/lib/commonjs/ui/hooks/mutations/useServicesMutations.js +193 -0
  24. package/lib/commonjs/ui/hooks/mutations/useServicesMutations.js.map +1 -0
  25. package/lib/commonjs/ui/hooks/queries/index.js +39 -0
  26. package/lib/commonjs/ui/hooks/queries/index.js.map +1 -0
  27. package/lib/commonjs/ui/hooks/queries/queryKeys.js +85 -0
  28. package/lib/commonjs/ui/hooks/queries/queryKeys.js.map +1 -0
  29. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +145 -0
  30. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -0
  31. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js +138 -0
  32. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js.map +1 -0
  33. package/lib/commonjs/ui/hooks/queryClient.js +117 -0
  34. package/lib/commonjs/ui/hooks/queryClient.js.map +1 -0
  35. package/lib/commonjs/ui/hooks/useIdentityMutations.js +111 -0
  36. package/lib/commonjs/ui/hooks/useIdentityMutations.js.map +1 -0
  37. package/lib/commonjs/ui/hooks/useProfileEditing.js +42 -58
  38. package/lib/commonjs/ui/hooks/useProfileEditing.js.map +1 -1
  39. package/lib/commonjs/ui/hooks/useQueryClient.js +20 -0
  40. package/lib/commonjs/ui/hooks/useQueryClient.js.map +1 -0
  41. package/lib/commonjs/ui/hooks/useSessionManagement.js +22 -2
  42. package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
  43. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +43 -42
  44. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  45. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +63 -58
  46. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  47. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +6 -6
  48. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  49. package/lib/commonjs/ui/stores/accountStore.js +57 -42
  50. package/lib/commonjs/ui/stores/accountStore.js.map +1 -1
  51. package/lib/commonjs/ui/stores/authStore.js +4 -25
  52. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  53. package/lib/module/core/mixins/OxyServices.user.js +14 -13
  54. package/lib/module/core/mixins/OxyServices.user.js.map +1 -1
  55. package/lib/module/crypto/keyManager.js +164 -3
  56. package/lib/module/crypto/keyManager.js.map +1 -1
  57. package/lib/module/crypto/signatureService.js +26 -0
  58. package/lib/module/crypto/signatureService.js.map +1 -1
  59. package/lib/module/ui/components/GroupedSection.js +1 -1
  60. package/lib/module/ui/components/GroupedSection.js.map +1 -1
  61. package/lib/module/ui/components/OxyProvider.js +72 -25
  62. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  63. package/lib/module/ui/components/profile/EditDisplayNameModal.js +1 -4
  64. package/lib/module/ui/components/profile/EditDisplayNameModal.js.map +1 -1
  65. package/lib/module/ui/context/OxyContext.js +174 -4
  66. package/lib/module/ui/context/OxyContext.js.map +1 -1
  67. package/lib/module/ui/context/hooks/useAuthOperations.js +148 -49
  68. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  69. package/lib/module/ui/context/hooks/useSessionManagement.js +22 -2
  70. package/lib/module/ui/context/hooks/useSessionManagement.js.map +1 -1
  71. package/lib/module/ui/hooks/mutations/index.js +6 -0
  72. package/lib/module/ui/hooks/mutations/index.js.map +1 -0
  73. package/lib/module/ui/hooks/mutations/useAccountMutations.js +308 -0
  74. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -0
  75. package/lib/module/ui/hooks/mutations/useServicesMutations.js +185 -0
  76. package/lib/module/ui/hooks/mutations/useServicesMutations.js.map +1 -0
  77. package/lib/module/ui/hooks/queries/index.js +7 -0
  78. package/lib/module/ui/hooks/queries/index.js.map +1 -0
  79. package/lib/module/ui/hooks/queries/queryKeys.js +78 -0
  80. package/lib/module/ui/hooks/queries/queryKeys.js.map +1 -0
  81. package/lib/module/ui/hooks/queries/useAccountQueries.js +136 -0
  82. package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -0
  83. package/lib/module/ui/hooks/queries/useServicesQueries.js +130 -0
  84. package/lib/module/ui/hooks/queries/useServicesQueries.js.map +1 -0
  85. package/lib/module/ui/hooks/queryClient.js +110 -0
  86. package/lib/module/ui/hooks/queryClient.js.map +1 -0
  87. package/lib/module/ui/hooks/useIdentityMutations.js +105 -0
  88. package/lib/module/ui/hooks/useIdentityMutations.js.map +1 -0
  89. package/lib/module/ui/hooks/useProfileEditing.js +43 -59
  90. package/lib/module/ui/hooks/useProfileEditing.js.map +1 -1
  91. package/lib/module/ui/hooks/useQueryClient.js +15 -0
  92. package/lib/module/ui/hooks/useQueryClient.js.map +1 -0
  93. package/lib/module/ui/hooks/useSessionManagement.js +22 -2
  94. package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
  95. package/lib/module/ui/screens/AccountOverviewScreen.js +43 -42
  96. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  97. package/lib/module/ui/screens/AccountSettingsScreen.js +63 -58
  98. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  99. package/lib/module/ui/screens/WelcomeNewUserScreen.js +6 -6
  100. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  101. package/lib/module/ui/stores/accountStore.js +57 -42
  102. package/lib/module/ui/stores/accountStore.js.map +1 -1
  103. package/lib/module/ui/stores/authStore.js +4 -25
  104. package/lib/module/ui/stores/authStore.js.map +1 -1
  105. package/lib/typescript/core/mixins/OxyServices.user.d.ts +4 -5
  106. package/lib/typescript/core/mixins/OxyServices.user.d.ts.map +1 -1
  107. package/lib/typescript/core/mixins/index.d.ts +0 -1
  108. package/lib/typescript/core/mixins/index.d.ts.map +1 -1
  109. package/lib/typescript/crypto/keyManager.d.ts +19 -2
  110. package/lib/typescript/crypto/keyManager.d.ts.map +1 -1
  111. package/lib/typescript/crypto/signatureService.d.ts +5 -0
  112. package/lib/typescript/crypto/signatureService.d.ts.map +1 -1
  113. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  114. package/lib/typescript/ui/components/profile/EditDisplayNameModal.d.ts.map +1 -1
  115. package/lib/typescript/ui/context/OxyContext.d.ts +4 -0
  116. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  117. package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  118. package/lib/typescript/ui/context/hooks/useSessionManagement.d.ts +3 -1
  119. package/lib/typescript/ui/context/hooks/useSessionManagement.d.ts.map +1 -1
  120. package/lib/typescript/ui/hooks/mutations/index.d.ts +3 -0
  121. package/lib/typescript/ui/hooks/mutations/index.d.ts.map +1 -0
  122. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts +25 -0
  123. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -0
  124. package/lib/typescript/ui/hooks/mutations/useServicesMutations.d.ts +23 -0
  125. package/lib/typescript/ui/hooks/mutations/useServicesMutations.d.ts.map +1 -0
  126. package/lib/typescript/ui/hooks/queries/index.d.ts +4 -0
  127. package/lib/typescript/ui/hooks/queries/index.d.ts.map +1 -0
  128. package/lib/typescript/ui/hooks/queries/queryKeys.d.ts +56 -0
  129. package/lib/typescript/ui/hooks/queries/queryKeys.d.ts.map +1 -0
  130. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts +41 -0
  131. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -0
  132. package/lib/typescript/ui/hooks/queries/useServicesQueries.d.ts +34 -0
  133. package/lib/typescript/ui/hooks/queries/useServicesQueries.d.ts.map +1 -0
  134. package/lib/typescript/ui/hooks/queryClient.d.ts +19 -0
  135. package/lib/typescript/ui/hooks/queryClient.d.ts.map +1 -0
  136. package/lib/typescript/ui/hooks/useIdentityMutations.d.ts +29 -0
  137. package/lib/typescript/ui/hooks/useIdentityMutations.d.ts.map +1 -0
  138. package/lib/typescript/ui/hooks/useProfileEditing.d.ts.map +1 -1
  139. package/lib/typescript/ui/hooks/useQueryClient.d.ts +7 -0
  140. package/lib/typescript/ui/hooks/useQueryClient.d.ts.map +1 -0
  141. package/lib/typescript/ui/hooks/useSessionManagement.d.ts +3 -1
  142. package/lib/typescript/ui/hooks/useSessionManagement.d.ts.map +1 -1
  143. package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
  144. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  145. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  146. package/lib/typescript/ui/stores/accountStore.d.ts.map +1 -1
  147. package/lib/typescript/ui/stores/authStore.d.ts +0 -4
  148. package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
  149. package/package.json +5 -4
  150. package/src/core/mixins/OxyServices.user.ts +17 -10
  151. package/src/crypto/keyManager.ts +177 -2
  152. package/src/crypto/signatureService.ts +30 -0
  153. package/src/ui/components/GroupedSection.tsx +1 -1
  154. package/src/ui/components/OxyProvider.tsx +91 -37
  155. package/src/ui/components/profile/EditDisplayNameModal.tsx +1 -3
  156. package/src/ui/context/OxyContext.tsx +185 -2
  157. package/src/ui/context/hooks/useAuthOperations.ts +171 -58
  158. package/src/ui/context/hooks/useSessionManagement.ts +24 -1
  159. package/src/ui/hooks/mutations/index.ts +4 -0
  160. package/src/ui/hooks/mutations/useAccountMutations.ts +277 -0
  161. package/src/ui/hooks/mutations/useServicesMutations.ts +164 -0
  162. package/src/ui/hooks/queries/index.ts +5 -0
  163. package/src/ui/hooks/queries/queryKeys.ts +73 -0
  164. package/src/ui/hooks/queries/useAccountQueries.ts +126 -0
  165. package/src/ui/hooks/queries/useServicesQueries.ts +121 -0
  166. package/src/ui/hooks/queryClient.ts +112 -0
  167. package/src/ui/hooks/useIdentityMutations.ts +115 -0
  168. package/src/ui/hooks/useProfileEditing.ts +46 -60
  169. package/src/ui/hooks/useQueryClient.ts +17 -0
  170. package/src/ui/hooks/useSessionManagement.ts +24 -1
  171. package/src/ui/screens/AccountOverviewScreen.tsx +38 -46
  172. package/src/ui/screens/AccountSettingsScreen.tsx +54 -54
  173. package/src/ui/screens/WelcomeNewUserScreen.tsx +13 -12
  174. package/src/ui/stores/accountStore.ts +54 -43
  175. package/src/ui/stores/authStore.ts +3 -17
@@ -39,6 +39,8 @@ import { EditLinksModal } from '../components/profile/EditLinksModal';
39
39
  import { getDisplayName } from '../utils/user-utils';
40
40
  import { TTLCache, registerCacheForCleanup } from '../../utils/cache';
41
41
  import { useOxy } from '../context/OxyContext';
42
+ import { useCurrentUser } from '../hooks/queries/useAccountQueries';
43
+ import { useUpdateProfile, useUploadAvatar } from '../hooks/mutations/useAccountMutations';
42
44
  import {
43
45
  SCREEN_PADDING_HORIZONTAL,
44
46
  SCREEN_PADDING_VERTICAL,
@@ -67,20 +69,24 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
67
69
  }) => {
68
70
  // Use useOxy() hook for OxyContext values
69
71
  const {
70
- user: userFromContext,
71
72
  oxyServices,
72
- isLoading: authLoading,
73
73
  isAuthenticated,
74
74
  activeSessionId,
75
75
  } = useOxy();
76
76
  const { t } = useI18n();
77
77
  const normalizedTheme = normalizeTheme(theme);
78
- const updateUser = useAuthStore((state) => state.updateUser);
79
- // Get user directly from store to ensure reactivity to avatar changes
80
- const user = useAuthStore((state) => state.user) || userFromContext;
78
+
79
+ // Use TanStack Query for user data
80
+ const { data: user, isLoading: userLoading } = useCurrentUser({ enabled: isAuthenticated });
81
+ const updateProfileMutation = useUpdateProfile();
82
+ const uploadAvatarMutation = useUploadAvatar();
83
+
84
+ // Fallback to store for backward compatibility
85
+ const userFromStore = useAuthStore((state) => state.user);
86
+ const finalUser = user || userFromStore;
81
87
  const [isLoading, setIsLoading] = useState(false);
82
- const [isSaving, setIsSaving] = useState(false);
83
- const [isUpdatingAvatar, setIsUpdatingAvatar] = useState(false);
88
+ const isSaving = updateProfileMutation.isPending;
89
+ const isUpdatingAvatar = uploadAvatarMutation.isPending;
84
90
  const [optimisticAvatarId, setOptimisticAvatarId] = useState<string | null>(null);
85
91
  const scrollViewRef = useRef<ScrollView>(null);
86
92
  const avatarSectionRef = useRef<View>(null);
@@ -202,9 +208,9 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
202
208
 
203
209
  // Load user data - only reset fields when user actually changes (not just avatar)
204
210
  useEffect(() => {
205
- if (user) {
206
- const currentUserId = user.id;
207
- const currentAvatar = typeof user.avatar === 'string' ? user.avatar : '';
211
+ if (finalUser) {
212
+ const currentUserId = finalUser.id;
213
+ const currentAvatar = typeof finalUser.avatar === 'string' ? finalUser.avatar : '';
208
214
  const isNewUser = previousUserIdRef.current !== currentUserId;
209
215
  const isAvatarOnlyUpdate = !isNewUser && previousUserIdRef.current === currentUserId &&
210
216
  previousAvatarRef.current !== currentAvatar &&
@@ -214,30 +220,30 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
214
220
  // Only reset all fields if it's a new user or first load
215
221
  // Skip reset if it's just an avatar update
216
222
  if (shouldInitialize && !isAvatarOnlyUpdate) {
217
- const userDisplayName = typeof user.name === 'string'
218
- ? user.name
219
- : user.name?.first || user.name?.full || '';
220
- const userLastName = typeof user.name === 'object' ? user.name?.last || '' : '';
223
+ const userDisplayName = typeof finalUser.name === 'string'
224
+ ? finalUser.name
225
+ : finalUser.name?.first || finalUser.name?.full || '';
226
+ const userLastName = typeof finalUser.name === 'object' ? finalUser.name?.last || '' : '';
221
227
  setDisplayName(userDisplayName);
222
228
  setLastName(userLastName);
223
- setUsername(user.username || '');
224
- setEmail(user.email || '');
225
- setBio(user.bio || '');
226
- setLocation(user.location || '');
229
+ setUsername(finalUser.username || '');
230
+ setEmail(finalUser.email || '');
231
+ setBio(finalUser.bio || '');
232
+ setLocation(finalUser.location || '');
227
233
 
228
234
  // Handle locations - convert single location to array format
229
- if (user.locations && Array.isArray(user.locations)) {
230
- setLocations(user.locations.map((loc, index) => ({
235
+ if (finalUser.locations && Array.isArray(finalUser.locations)) {
236
+ setLocations(finalUser.locations.map((loc, index) => ({
231
237
  id: loc.id || `existing-${index}`,
232
238
  name: loc.name,
233
239
  label: loc.label,
234
240
  coordinates: loc.coordinates
235
241
  })));
236
- } else if (user.location) {
242
+ } else if (finalUser.location) {
237
243
  // Convert single location string to array format
238
244
  setLocations([{
239
245
  id: 'existing-0',
240
- name: user.location,
246
+ name: finalUser.location,
241
247
  label: 'Location'
242
248
  }]);
243
249
  } else {
@@ -245,16 +251,16 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
245
251
  }
246
252
 
247
253
  // Handle links - simple and direct like other fields
248
- if (user.linksMetadata && Array.isArray(user.linksMetadata)) {
249
- const urls = user.linksMetadata.map(l => l.url);
254
+ if (finalUser.linksMetadata && Array.isArray(finalUser.linksMetadata)) {
255
+ const urls = finalUser.linksMetadata.map(l => l.url);
250
256
  setLinks(urls);
251
- const metadataWithIds = user.linksMetadata.map((link, index) => ({
257
+ const metadataWithIds = finalUser.linksMetadata.map((link, index) => ({
252
258
  ...link,
253
259
  id: link.id || `existing-${index}`
254
260
  }));
255
261
  setLinksMetadata(metadataWithIds);
256
- } else if (Array.isArray(user.links)) {
257
- const simpleLinks = user.links.map(l => typeof l === 'string' ? l : l.link).filter(Boolean);
262
+ } else if (Array.isArray(finalUser.links)) {
263
+ const simpleLinks = finalUser.links.map(l => typeof l === 'string' ? l : l.link).filter(Boolean);
258
264
  setLinks(simpleLinks);
259
265
  const linksWithMetadata = simpleLinks.map((url, index) => ({
260
266
  url,
@@ -264,12 +270,12 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
264
270
  id: `existing-${index}`
265
271
  }));
266
272
  setLinksMetadata(linksWithMetadata);
267
- } else if (user.website) {
268
- setLinks([user.website]);
273
+ } else if (finalUser.website) {
274
+ setLinks([finalUser.website]);
269
275
  setLinksMetadata([{
270
- url: user.website,
271
- title: user.website.replace(/^https?:\/\//, '').replace(/\/$/, ''),
272
- description: `Link to ${user.website}`,
276
+ url: finalUser.website,
277
+ title: finalUser.website.replace(/^https?:\/\//, '').replace(/\/$/, ''),
278
+ description: `Link to ${finalUser.website}`,
273
279
  image: undefined,
274
280
  id: 'existing-0'
275
281
  }]);
@@ -298,7 +304,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
298
304
  previousUserIdRef.current = currentUserId;
299
305
  previousAvatarRef.current = currentAvatar;
300
306
  }
301
- }, [user, avatarFileId, isUpdatingAvatar, optimisticAvatarId]);
307
+ }, [finalUser, avatarFileId, isUpdatingAvatar, optimisticAvatarId]);
302
308
 
303
309
  // Set initial editing field if provided via props (e.g., from navigation)
304
310
  // Use a ref to track if we've already set the initial field to avoid loops
@@ -358,32 +364,31 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
358
364
  const saveField = useCallback(async (field: string | null) => {
359
365
  if (!field) return;
360
366
 
361
- setIsSaving(true);
362
367
  try {
363
368
  switch (field) {
364
369
  case 'displayName':
365
- await updateUser({ name: { first: tempDisplayName, last: tempLastName } }, oxyServices);
370
+ await updateProfileMutation.mutateAsync({ name: { first: tempDisplayName, last: tempLastName } });
366
371
  setDisplayName(tempDisplayName);
367
372
  setLastName(tempLastName);
368
373
  break;
369
374
  case 'username':
370
- await updateUser({ username: tempUsername }, oxyServices);
375
+ await updateProfileMutation.mutateAsync({ username: tempUsername });
371
376
  setUsername(tempUsername);
372
377
  break;
373
378
  case 'email':
374
- await updateUser({ email: tempEmail }, oxyServices);
379
+ await updateProfileMutation.mutateAsync({ email: tempEmail });
375
380
  setEmail(tempEmail);
376
381
  break;
377
382
  case 'bio':
378
- await updateUser({ bio: tempBio }, oxyServices);
383
+ await updateProfileMutation.mutateAsync({ bio: tempBio });
379
384
  setBio(tempBio);
380
385
  break;
381
386
  case 'location':
382
- await updateUser({ locations: tempLocations }, oxyServices);
387
+ await updateProfileMutation.mutateAsync({ locations: tempLocations });
383
388
  setLocations(tempLocations);
384
389
  break;
385
390
  case 'links':
386
- await updateUser({ linksMetadata: tempLinksWithMetadata }, oxyServices);
391
+ await updateProfileMutation.mutateAsync({ linksMetadata: tempLinksWithMetadata });
387
392
  setLinksMetadata(tempLinksWithMetadata);
388
393
  setLinks(tempLinksWithMetadata.map(l => l.url));
389
394
  break;
@@ -391,11 +396,9 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
391
396
  setEditingField(null);
392
397
  toast.success(t('editProfile.toasts.saved') || 'Saved');
393
398
  } catch (error: any) {
394
- toast.error(error?.message || (t('editProfile.toasts.saveFailed') || 'Failed to save'));
395
- } finally {
396
- setIsSaving(false);
399
+ // Error is already handled by mutation's onError
397
400
  }
398
- }, [tempDisplayName, tempLastName, tempUsername, tempEmail, tempBio, tempLocations, tempLinksWithMetadata, updateUser, oxyServices, t]);
401
+ }, [tempDisplayName, tempLastName, tempUsername, tempEmail, tempBio, tempLocations, tempLinksWithMetadata, updateProfileMutation, t]);
399
402
 
400
403
  // Helper to get current value for a field
401
404
  const getFieldCurrentValue = useCallback((field: string): string => {
@@ -452,10 +455,9 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
452
455
  }, [initialSection, sectionYPositions]);
453
456
 
454
457
  const handleSave = async () => {
455
- if (!user) return;
458
+ if (!finalUser) return;
456
459
 
457
460
  try {
458
- setIsSaving(true);
459
461
  animateSaveButton(0.95); // Scale down slightly for animation
460
462
 
461
463
  const updates: Record<string, any> = {
@@ -474,11 +476,11 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
474
476
  }
475
477
 
476
478
  // Handle avatar
477
- if (avatarFileId !== (typeof user.avatar === 'string' ? user.avatar : '')) {
479
+ if (avatarFileId !== (typeof finalUser.avatar === 'string' ? finalUser.avatar : '')) {
478
480
  updates.avatar = avatarFileId;
479
481
  }
480
482
 
481
- await updateUser(updates, oxyServices);
483
+ await updateProfileMutation.mutateAsync(updates);
482
484
  toast.success(t('editProfile.toasts.profileUpdated') || 'Profile updated successfully');
483
485
 
484
486
  animateSaveButton(1); // Scale back to normal
@@ -489,10 +491,8 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
489
491
  goBack();
490
492
  }
491
493
  } catch (error: any) {
492
- toast.error(error.message || t('editProfile.toasts.updateFailed') || 'Failed to update profile');
494
+ // Error is already handled by mutation's onError
493
495
  animateSaveButton(1); // Scale back to normal on error
494
- } finally {
495
- setIsSaving(false);
496
496
  }
497
497
  };
498
498
 
@@ -739,7 +739,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
739
739
  };
740
740
 
741
741
  // Memoize display name for avatar
742
- const displayNameForAvatar = useMemo(() => getDisplayName(user), [user]);
742
+ const displayNameForAvatar = useMemo(() => getDisplayName(finalUser), [finalUser]);
743
743
 
744
744
  // Legacy renderEditingField function (fallback)
745
745
  const renderEditingField = (type: string | null) => {
@@ -1110,7 +1110,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
1110
1110
 
1111
1111
 
1112
1112
 
1113
- if (isLoading || !isAuthenticated) {
1113
+ if (userLoading || !isAuthenticated) {
1114
1114
  return (
1115
1115
  <View style={[styles.container, {
1116
1116
  backgroundColor: themeStyles.backgroundColor,
@@ -1418,7 +1418,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
1418
1418
  iconColor: colors.sidebarIconHome,
1419
1419
  title: t('editProfile.items.previewProfile.title') || 'Preview Profile',
1420
1420
  subtitle: t('editProfile.items.previewProfile.subtitle') || 'See how your profile looks to others',
1421
- onPress: () => navigate?.('Profile', { userId: user?.id }),
1421
+ onPress: () => navigate?.('Profile', { userId: finalUser?.id }),
1422
1422
  },
1423
1423
  {
1424
1424
  id: 'privacy-settings',
@@ -11,6 +11,7 @@ import { normalizeTheme } from '../utils/themeUtils';
11
11
  import GroupedPillButtons from '../components/internal/GroupedPillButtons';
12
12
  import { useI18n } from '../hooks/useI18n';
13
13
  import { useOxy } from '../context/OxyContext';
14
+ import { useUpdateProfile } from '../hooks/mutations/useAccountMutations';
14
15
 
15
16
  const GAP = 12;
16
17
  const INNER_GAP = 8;
@@ -62,7 +63,7 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: any }> = ({
62
63
  // Use useOxy() hook for OxyContext values
63
64
  const { user, oxyServices } = useOxy();
64
65
  const { t } = useI18n();
65
- const updateUser = useAuthStore(s => s.updateUser);
66
+ const updateProfileMutation = useUpdateProfile();
66
67
  const currentUser = user || newUser; // fallback
67
68
  const normalizedTheme = normalizeTheme(theme);
68
69
  const colors = useThemeColors(normalizedTheme);
@@ -161,11 +162,11 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: any }> = ({
161
162
 
162
163
  // Update the avatar immediately in local state
163
164
  setSelectedAvatarId(file.id);
164
-
165
- // Update user in store
166
- await updateUser({ avatar: file.id }, oxyServices);
165
+
166
+ // Update user using TanStack Query mutation
167
+ await updateProfileMutation.mutateAsync({ avatar: file.id });
167
168
  toast.success(t('editProfile.toasts.avatarUpdated') || 'Avatar updated');
168
-
169
+
169
170
  // Ensure we stay on the avatar step
170
171
  if (avatarStepIndex >= 0 && currentStep !== avatarStepIndex) {
171
172
  animateToStepCallback(avatarStepIndex);
@@ -175,7 +176,7 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: any }> = ({
175
176
  }
176
177
  }
177
178
  });
178
- }, [navigate, updateUser, oxyServices, currentStep, avatarStepIndex, animateToStepCallback, t]);
179
+ }, [navigate, updateProfileMutation, oxyServices, currentStep, avatarStepIndex, animateToStepCallback, t]);
179
180
 
180
181
  const step = steps[currentStep];
181
182
  const pillButtons = useMemo(() => {
@@ -233,13 +234,13 @@ const WelcomeNewUserScreen: React.FC<BaseScreenProps & { newUser?: any }> = ({
233
234
  )}
234
235
  {step.showAvatar && (
235
236
  <View style={[styles.avatarSection, styles.sectionSpacing]}>
236
- <Avatar
237
- size={120}
238
- name={currentUser?.name?.full || currentUser?.name?.first || currentUser?.username}
239
- uri={avatarUri}
240
-
237
+ <Avatar
238
+ size={120}
239
+ name={currentUser?.name?.full || currentUser?.name?.first || currentUser?.username}
240
+ uri={avatarUri}
241
+
241
242
  backgroundColor={colors.primary + '20'}
242
- style={styles.avatar}
243
+ style={styles.avatar}
243
244
  />
244
245
  <TouchableOpacity style={[styles.changeAvatarButton, { backgroundColor: colors.primary }]} onPress={openAvatarPicker}>
245
246
  <Ionicons name="image-outline" size={18} color="#FFFFFF" />
@@ -209,64 +209,75 @@ export const useAccountStore = create<AccountState>((set, get) => ({
209
209
  return;
210
210
  }
211
211
 
212
- const existingMap = new Map(existingAccounts.map(a => [a.sessionId, a]));
213
- for (const account of Object.values(state.accounts)) {
214
- existingMap.set(account.sessionId, account);
215
- }
216
-
217
- const missingSessionIds = uniqueSessionIds.filter(id => !existingMap.has(id));
218
-
219
- if (missingSessionIds.length === 0) {
220
- const ordered = uniqueSessionIds
221
- .map(id => existingMap.get(id))
222
- .filter((acc): acc is QuickAccount => acc !== undefined);
223
- get().setAccounts(ordered);
224
- return;
225
- }
226
-
227
- if (state.loading) {
228
- return;
229
- }
230
-
231
- set({ loading: true, error: null });
232
-
212
+ // Try to get data from TanStack Query cache first
233
213
  try {
234
- const batchResults = await oxyServices.getUsersBySessions(missingSessionIds);
214
+ // This will be called from a component, so we need to access queryClient differently
215
+ // For now, we'll keep the API call but optimize it
216
+ const existingMap = new Map(existingAccounts.map(a => [a.sessionId, a]));
217
+ for (const account of Object.values(state.accounts)) {
218
+ existingMap.set(account.sessionId, account);
219
+ }
235
220
 
236
- const accountMap = new Map<string, QuickAccount>();
221
+ const missingSessionIds = uniqueSessionIds.filter(id => !existingMap.has(id));
237
222
 
238
- for (const { sessionId, user: userData } of batchResults) {
239
- if (userData && !accountMap.has(sessionId)) {
240
- const existing = existingMap.get(sessionId);
241
- accountMap.set(sessionId, createQuickAccount(sessionId, userData, existing, oxyServices));
242
- }
223
+ if (missingSessionIds.length === 0) {
224
+ const ordered = uniqueSessionIds
225
+ .map(id => existingMap.get(id))
226
+ .filter((acc): acc is QuickAccount => acc !== undefined);
227
+ get().setAccounts(ordered);
228
+ return;
243
229
  }
244
230
 
245
- for (const [sessionId, account] of accountMap) {
246
- existingMap.set(sessionId, account);
231
+ if (state.loading) {
232
+ return;
247
233
  }
248
234
 
249
- const orderToUse = preserveOrder ? uniqueSessionIds : [...uniqueSessionIds, ...state.accountOrder];
250
- const seen = new Set<string>();
251
- const ordered: QuickAccount[] = [];
235
+ set({ loading: true, error: null });
252
236
 
253
- for (const sessionId of orderToUse) {
254
- if (seen.has(sessionId)) continue;
255
- seen.add(sessionId);
237
+ try {
238
+ const batchResults = await oxyServices.getUsersBySessions(missingSessionIds);
239
+
240
+ const accountMap = new Map<string, QuickAccount>();
241
+
242
+ for (const { sessionId, user: userData } of batchResults) {
243
+ if (userData && !accountMap.has(sessionId)) {
244
+ const existing = existingMap.get(sessionId);
245
+ accountMap.set(sessionId, createQuickAccount(sessionId, userData, existing, oxyServices));
246
+ }
247
+ }
248
+
249
+ for (const [sessionId, account] of accountMap) {
250
+ existingMap.set(sessionId, account);
251
+ }
256
252
 
257
- const account = existingMap.get(sessionId);
258
- if (account) ordered.push(account);
253
+ const orderToUse = preserveOrder ? uniqueSessionIds : [...uniqueSessionIds, ...state.accountOrder];
254
+ const seen = new Set<string>();
255
+ const ordered: QuickAccount[] = [];
256
+
257
+ for (const sessionId of orderToUse) {
258
+ if (seen.has(sessionId)) continue;
259
+ seen.add(sessionId);
260
+
261
+ const account = existingMap.get(sessionId);
262
+ if (account) ordered.push(account);
263
+ }
264
+
265
+ get().setAccounts(ordered);
266
+ } catch (error) {
267
+ const errorMessage = error instanceof Error ? error.message : 'Failed to load accounts';
268
+ if (__DEV__) {
269
+ console.error('AccountStore: Failed to load accounts:', error);
270
+ }
271
+ set({ error: errorMessage });
272
+ } finally {
273
+ set({ loading: false });
259
274
  }
260
-
261
- get().setAccounts(ordered);
262
275
  } catch (error) {
263
276
  const errorMessage = error instanceof Error ? error.message : 'Failed to load accounts';
264
277
  if (__DEV__) {
265
278
  console.error('AccountStore: Failed to load accounts:', error);
266
279
  }
267
- set({ error: errorMessage });
268
- } finally {
269
- set({ loading: false });
280
+ set({ error: errorMessage, loading: false });
270
281
  }
271
282
  },
272
283
 
@@ -16,7 +16,6 @@ export interface AuthState {
16
16
  loginFailure: (error: string) => void;
17
17
  logout: () => void;
18
18
  fetchUser: (oxyServices: { getCurrentUser: () => Promise<User> }, forceRefresh?: boolean) => Promise<void>;
19
- updateUser: (updates: Partial<User>, oxyServices: { updateProfile: (updates: Partial<User>) => Promise<User>; getCurrentUser: () => Promise<User> }) => Promise<void>;
20
19
  setUser: (user: User) => void; // Direct user setter for caching
21
20
 
22
21
  // Identity sync actions
@@ -47,7 +46,9 @@ export const useAuthStore = create<AuthState>((set: (state: Partial<AuthState>)
47
46
  user: null,
48
47
  isAuthenticated: false,
49
48
  lastUserFetch: null,
50
- // Keep identity sync state - user might still have local identity
49
+ // Reset identity sync state when logging out (for accounts app, identity = account)
50
+ isIdentitySynced: false,
51
+ isSyncing: false,
51
52
  }),
52
53
  setUser: (user: User) => set({ user, lastUserFetch: Date.now() }),
53
54
 
@@ -80,19 +81,4 @@ export const useAuthStore = create<AuthState>((set: (state: Partial<AuthState>)
80
81
  set({ error: errorMessage, isLoading: false });
81
82
  }
82
83
  },
83
- updateUser: async (updates, oxyServices) => {
84
- set({ isLoading: true, error: null });
85
- try {
86
- await oxyServices.updateProfile(updates);
87
- // Immediately fetch the latest user data after update
88
- // Use arrow function to preserve 'this' context
89
- await useAuthStore.getState().fetchUser({ getCurrentUser: () => oxyServices.getCurrentUser() }, true);
90
- } catch (error) {
91
- const errorMessage = error instanceof Error ? error.message : 'Failed to update user';
92
- if (__DEV__) {
93
- console.error('AuthStore: Error updating user:', error);
94
- }
95
- set({ error: errorMessage, isLoading: false });
96
- }
97
- },
98
84
  }));