@oxyhq/services 5.16.28 → 5.16.30

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 (111) hide show
  1. package/lib/commonjs/crypto/keyManager.js +3 -0
  2. package/lib/commonjs/crypto/keyManager.js.map +1 -1
  3. package/lib/commonjs/index.js +64 -0
  4. package/lib/commonjs/index.js.map +1 -1
  5. package/lib/commonjs/ui/context/OxyContext.js +70 -17
  6. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  7. package/lib/commonjs/ui/hooks/auth/index.js +37 -0
  8. package/lib/commonjs/ui/hooks/auth/index.js.map +1 -0
  9. package/lib/commonjs/ui/hooks/auth/useUsernameValidation.js +171 -0
  10. package/lib/commonjs/ui/hooks/auth/useUsernameValidation.js.map +1 -0
  11. package/lib/commonjs/ui/hooks/index.js +20 -0
  12. package/lib/commonjs/ui/hooks/index.js.map +1 -1
  13. package/lib/commonjs/ui/hooks/mutations/index.js +12 -0
  14. package/lib/commonjs/ui/hooks/mutations/index.js.map +1 -1
  15. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +45 -1
  16. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  17. package/lib/commonjs/ui/hooks/queries/index.js +12 -0
  18. package/lib/commonjs/ui/hooks/queries/index.js.map +1 -1
  19. package/lib/commonjs/ui/hooks/queries/queryKeys.js +3 -1
  20. package/lib/commonjs/ui/hooks/queries/queryKeys.js.map +1 -1
  21. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +43 -1
  22. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
  23. package/lib/commonjs/ui/hooks/useTransferQueries.js +1 -64
  24. package/lib/commonjs/ui/hooks/useTransferQueries.js.map +1 -1
  25. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js +76 -97
  26. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js.map +1 -1
  27. package/lib/commonjs/ui/stores/transferStore.js +1 -9
  28. package/lib/commonjs/ui/stores/transferStore.js.map +1 -1
  29. package/lib/module/crypto/keyManager.js +3 -0
  30. package/lib/module/crypto/keyManager.js.map +1 -1
  31. package/lib/module/index.js +3 -3
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/module/ui/context/OxyContext.js +70 -17
  34. package/lib/module/ui/context/OxyContext.js.map +1 -1
  35. package/lib/module/ui/hooks/auth/index.js +7 -0
  36. package/lib/module/ui/hooks/auth/index.js.map +1 -0
  37. package/lib/module/ui/hooks/auth/useUsernameValidation.js +167 -0
  38. package/lib/module/ui/hooks/auth/useUsernameValidation.js.map +1 -0
  39. package/lib/module/ui/hooks/index.js +1 -0
  40. package/lib/module/ui/hooks/index.js.map +1 -1
  41. package/lib/module/ui/hooks/mutations/index.js +1 -1
  42. package/lib/module/ui/hooks/mutations/index.js.map +1 -1
  43. package/lib/module/ui/hooks/mutations/useAccountMutations.js +42 -0
  44. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  45. package/lib/module/ui/hooks/queries/index.js +1 -1
  46. package/lib/module/ui/hooks/queries/index.js.map +1 -1
  47. package/lib/module/ui/hooks/queries/queryKeys.js +3 -1
  48. package/lib/module/ui/hooks/queries/queryKeys.js.map +1 -1
  49. package/lib/module/ui/hooks/queries/useAccountQueries.js +40 -0
  50. package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
  51. package/lib/module/ui/hooks/useTransferQueries.js +0 -61
  52. package/lib/module/ui/hooks/useTransferQueries.js.map +1 -1
  53. package/lib/module/ui/screens/PrivacySettingsScreen.js +77 -98
  54. package/lib/module/ui/screens/PrivacySettingsScreen.js.map +1 -1
  55. package/lib/module/ui/stores/transferStore.js +0 -7
  56. package/lib/module/ui/stores/transferStore.js.map +1 -1
  57. package/lib/typescript/crypto/keyManager.d.ts +2 -1
  58. package/lib/typescript/crypto/keyManager.d.ts.map +1 -1
  59. package/lib/typescript/index.d.ts +4 -2
  60. package/lib/typescript/index.d.ts.map +1 -1
  61. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  62. package/lib/typescript/ui/hooks/auth/index.d.ts +6 -0
  63. package/lib/typescript/ui/hooks/auth/index.d.ts.map +1 -0
  64. package/lib/typescript/ui/hooks/auth/useUsernameValidation.d.ts +32 -0
  65. package/lib/typescript/ui/hooks/auth/useUsernameValidation.d.ts.map +1 -0
  66. package/lib/typescript/ui/hooks/index.d.ts +1 -0
  67. package/lib/typescript/ui/hooks/index.d.ts.map +1 -1
  68. package/lib/typescript/ui/hooks/mutations/index.d.ts +1 -1
  69. package/lib/typescript/ui/hooks/mutations/index.d.ts.map +1 -1
  70. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts +12 -0
  71. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  72. package/lib/typescript/ui/hooks/queries/index.d.ts +1 -1
  73. package/lib/typescript/ui/hooks/queries/index.d.ts.map +1 -1
  74. package/lib/typescript/ui/hooks/queries/queryKeys.d.ts +2 -0
  75. package/lib/typescript/ui/hooks/queries/queryKeys.d.ts.map +1 -1
  76. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts +12 -0
  77. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  78. package/lib/typescript/ui/hooks/useTransferQueries.d.ts +0 -28
  79. package/lib/typescript/ui/hooks/useTransferQueries.d.ts.map +1 -1
  80. package/lib/typescript/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
  81. package/lib/typescript/ui/stores/transferStore.d.ts +0 -4
  82. package/lib/typescript/ui/stores/transferStore.d.ts.map +1 -1
  83. package/package.json +1 -1
  84. package/src/crypto/keyManager.ts +5 -1
  85. package/src/index.ts +6 -0
  86. package/src/ui/context/OxyContext.tsx +67 -13
  87. package/src/ui/hooks/auth/index.ts +6 -0
  88. package/src/ui/hooks/auth/useUsernameValidation.ts +177 -0
  89. package/src/ui/hooks/index.ts +2 -1
  90. package/src/ui/hooks/mutations/index.ts +2 -0
  91. package/src/ui/hooks/mutations/useAccountMutations.ts +36 -0
  92. package/src/ui/hooks/queries/index.ts +2 -0
  93. package/src/ui/hooks/queries/queryKeys.ts +2 -0
  94. package/src/ui/hooks/queries/useAccountQueries.ts +34 -0
  95. package/src/ui/hooks/useTransferQueries.ts +1 -67
  96. package/src/ui/screens/PrivacySettingsScreen.tsx +67 -101
  97. package/src/ui/stores/transferStore.ts +0 -6
  98. package/lib/commonjs/ui/context/hooks/useSessionManagement.js +0 -281
  99. package/lib/commonjs/ui/context/hooks/useSessionManagement.js.map +0 -1
  100. package/lib/commonjs/ui/context/hooks/useStorage.js +0 -79
  101. package/lib/commonjs/ui/context/hooks/useStorage.js.map +0 -1
  102. package/lib/module/ui/context/hooks/useSessionManagement.js +0 -276
  103. package/lib/module/ui/context/hooks/useSessionManagement.js.map +0 -1
  104. package/lib/module/ui/context/hooks/useStorage.js +0 -74
  105. package/lib/module/ui/context/hooks/useStorage.js.map +0 -1
  106. package/lib/typescript/ui/context/hooks/useSessionManagement.d.ts +0 -41
  107. package/lib/typescript/ui/context/hooks/useSessionManagement.d.ts.map +0 -1
  108. package/lib/typescript/ui/context/hooks/useStorage.d.ts +0 -22
  109. package/lib/typescript/ui/context/hooks/useStorage.d.ts.map +0 -1
  110. package/src/ui/context/hooks/useSessionManagement.ts +0 -401
  111. package/src/ui/context/hooks/useStorage.ts +0 -104
@@ -0,0 +1,177 @@
1
+ import { useMemo } from 'react';
2
+ import { useQuery } from '@tanstack/react-query';
3
+ import type { OxyServices } from '../../../core';
4
+ import { handleHttpError, ErrorCodes } from '../../../utils/errorUtils';
5
+ import { useDebounce } from '../../../utils/hookUtils';
6
+
7
+ /**
8
+ * Username validation constants
9
+ */
10
+ export const USERNAME_MIN_LENGTH = 4;
11
+ export const USERNAME_REGEX = /^[a-z0-9]+$/i;
12
+ export const USERNAME_FORMAT_ERROR = 'You can use a-z, 0-9. Minimum length is 4 characters.';
13
+ export const USERNAME_DEBOUNCE_MS = 500;
14
+
15
+ /**
16
+ * Username validation result interface
17
+ */
18
+ export interface UsernameValidationResult {
19
+ isValid: boolean;
20
+ isAvailable: boolean | null; // null = not checked yet
21
+ error: string | null;
22
+ isChecking: boolean;
23
+ }
24
+
25
+ /**
26
+ * Validate username format using services validation utilities
27
+ */
28
+ function validateUsernameFormat(username: string): boolean {
29
+ // Use stricter validation: lowercase alphanumeric only, min 4 chars
30
+ return username.length >= USERNAME_MIN_LENGTH && USERNAME_REGEX.test(username);
31
+ }
32
+
33
+ /**
34
+ * Check if an error is a network or timeout error
35
+ */
36
+ function isNetworkOrTimeoutError(error: unknown): boolean {
37
+ const apiError = handleHttpError(error);
38
+ return (
39
+ apiError.code === ErrorCodes.NETWORK_ERROR ||
40
+ apiError.code === ErrorCodes.TIMEOUT ||
41
+ apiError.code === ErrorCodes.CONNECTION_FAILED
42
+ );
43
+ }
44
+
45
+ /**
46
+ * Extract error message from an unknown error shape
47
+ */
48
+ function extractAuthErrorMessage(error: unknown, fallbackMessage = 'An error occurred'): string {
49
+ const apiError = handleHttpError(error);
50
+ return apiError.message || fallbackMessage;
51
+ }
52
+
53
+ /**
54
+ * Hook for username validation with debouncing and availability checking
55
+ *
56
+ * Uses TanStack Query for efficient API calls with:
57
+ * - Automatic request cancellation when username changes
58
+ * - Built-in caching (same username checked multiple times = cached result)
59
+ * - Request deduplication (multiple components checking same username = single request)
60
+ * - Proper error handling
61
+ *
62
+ * @param username - The username to validate
63
+ * @param oxyServices - OxyServices instance for API calls
64
+ * @returns Username validation state and result
65
+ */
66
+ export function useUsernameValidation(
67
+ username: string,
68
+ oxyServices: OxyServices | null
69
+ ): UsernameValidationResult {
70
+ // Debounce the username input to avoid excessive API calls
71
+ const debouncedUsername = useDebounce(username.trim().toLowerCase(), USERNAME_DEBOUNCE_MS);
72
+
73
+ // Validate format synchronously (no API call needed)
74
+ const isValid = useMemo(() => validateUsernameFormat(username), [username]);
75
+
76
+ // Determine if we should check availability
77
+ const shouldCheckAvailability = useMemo(() => {
78
+ if (!debouncedUsername || debouncedUsername.length < USERNAME_MIN_LENGTH) {
79
+ return false;
80
+ }
81
+ return validateUsernameFormat(debouncedUsername);
82
+ }, [debouncedUsername]);
83
+
84
+ // Use TanStack Query for the API call
85
+ // This provides automatic caching, request cancellation, and deduplication
86
+ const {
87
+ data: availabilityResult,
88
+ isLoading: isChecking,
89
+ error: queryError,
90
+ isFetching,
91
+ } = useQuery({
92
+ queryKey: ['username', 'availability', debouncedUsername],
93
+ queryFn: async () => {
94
+ if (!oxyServices) {
95
+ throw new Error('OxyServices not available');
96
+ }
97
+ return await oxyServices.checkUsernameAvailability(debouncedUsername);
98
+ },
99
+ enabled: shouldCheckAvailability && !!oxyServices,
100
+ staleTime: 5 * 60 * 1000, // Cache for 5 minutes (usernames don't change often)
101
+ gcTime: 30 * 60 * 1000, // Keep in cache for 30 minutes
102
+ retry: (failureCount, error) => {
103
+ // Don't retry on network/timeout errors (user might be offline)
104
+ if (isNetworkOrTimeoutError(error)) {
105
+ return false;
106
+ }
107
+ // Retry up to 2 times for other errors
108
+ return failureCount < 2;
109
+ },
110
+ retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 3000),
111
+ });
112
+
113
+ // Compute the result based on validation and query state
114
+ return useMemo(() => {
115
+ // If username is too short or invalid format, return early validation
116
+ if (!username || username.length < USERNAME_MIN_LENGTH) {
117
+ return {
118
+ isValid: false,
119
+ isAvailable: null,
120
+ error: null,
121
+ isChecking: false,
122
+ };
123
+ }
124
+
125
+ if (!isValid) {
126
+ return {
127
+ isValid: false,
128
+ isAvailable: false,
129
+ error: USERNAME_FORMAT_ERROR,
130
+ isChecking: false,
131
+ };
132
+ }
133
+
134
+ // If we're not checking yet (debounce period), show checking state only if user is typing
135
+ const isCurrentlyChecking = isChecking || isFetching;
136
+
137
+ // Handle network/timeout errors gracefully
138
+ if (queryError && isNetworkOrTimeoutError(queryError)) {
139
+ // Allow proceeding if offline/network issue (optimistic)
140
+ return {
141
+ isValid: true,
142
+ isAvailable: true, // Optimistic: allow proceeding
143
+ error: null,
144
+ isChecking: false,
145
+ };
146
+ }
147
+
148
+ // Handle other errors
149
+ if (queryError) {
150
+ return {
151
+ isValid: true,
152
+ isAvailable: false,
153
+ error: extractAuthErrorMessage(queryError, 'Failed to check username availability'),
154
+ isChecking: false,
155
+ };
156
+ }
157
+
158
+ // If we have a result, use it
159
+ if (availabilityResult) {
160
+ return {
161
+ isValid: true,
162
+ isAvailable: availabilityResult.available,
163
+ error: availabilityResult.available ? null : (availabilityResult.message || 'Username is already taken'),
164
+ isChecking: false,
165
+ };
166
+ }
167
+
168
+ // Still checking (or waiting for debounce)
169
+ return {
170
+ isValid: true,
171
+ isAvailable: null,
172
+ error: null,
173
+ isChecking: isCurrentlyChecking,
174
+ };
175
+ }, [username, isValid, availabilityResult, isChecking, isFetching, queryError]);
176
+ }
177
+
@@ -1,4 +1,5 @@
1
1
  export { useFollow, useFollowerCounts } from './useFollow';
2
2
  export { useFileDownloadUrl, setOxyFileUrlInstance } from './useFileDownloadUrl';
3
3
  export { useThemeStyles } from './useThemeStyles';
4
- export { useThemeColors } from './useThemeColors';
4
+ export { useThemeColors } from './useThemeColors';
5
+ export * from './auth';
@@ -12,6 +12,8 @@ export {
12
12
  useUpdateAccountSettings,
13
13
  useUpdatePrivacySettings,
14
14
  useUploadFile,
15
+ useUnblockUser,
16
+ useUnrestrictUser,
15
17
  } from './useAccountMutations';
16
18
 
17
19
  // Service mutation hooks (sessions, devices)
@@ -499,3 +499,39 @@ export const useUploadFile = () => {
499
499
  });
500
500
  };
501
501
 
502
+ /**
503
+ * Unblock a user with query invalidation
504
+ */
505
+ export const useUnblockUser = () => {
506
+ const { oxyServices } = useOxy();
507
+ const queryClient = useQueryClient();
508
+
509
+ return useMutation({
510
+ mutationFn: async (userId: string) => {
511
+ return await oxyServices.unblockUser(userId);
512
+ },
513
+ onSuccess: () => {
514
+ // Invalidate blocked users query to refetch the list
515
+ queryClient.invalidateQueries({ queryKey: queryKeys.privacy.blocked() });
516
+ },
517
+ });
518
+ };
519
+
520
+ /**
521
+ * Unrestrict a user with query invalidation
522
+ */
523
+ export const useUnrestrictUser = () => {
524
+ const { oxyServices } = useOxy();
525
+ const queryClient = useQueryClient();
526
+
527
+ return useMutation({
528
+ mutationFn: async (userId: string) => {
529
+ return await oxyServices.unrestrictUser(userId);
530
+ },
531
+ onSuccess: () => {
532
+ // Invalidate restricted users query to refetch the list
533
+ queryClient.invalidateQueries({ queryKey: queryKeys.privacy.restricted() });
534
+ },
535
+ });
536
+ };
537
+
@@ -14,6 +14,8 @@ export {
14
14
  useUserByUsername,
15
15
  useUsersBySessions,
16
16
  usePrivacySettings,
17
+ useBlockedUsers,
18
+ useRestrictedUsers,
17
19
  } from './useAccountQueries';
18
20
 
19
21
  // Service query hooks (sessions, devices, security)
@@ -53,6 +53,8 @@ export const queryKeys = {
53
53
  privacy: {
54
54
  all: ['privacy'] as const,
55
55
  settings: (userId?: string) => [...queryKeys.privacy.all, 'settings', userId || 'current'] as const,
56
+ blocked: () => [...queryKeys.privacy.all, 'blocked'] as const,
57
+ restricted: () => [...queryKeys.privacy.all, 'restricted'] as const,
56
58
  },
57
59
 
58
60
  // Security activity queries
@@ -195,3 +195,37 @@ export const usePrivacySettings = (userId?: string, options?: { enabled?: boolea
195
195
  });
196
196
  };
197
197
 
198
+ /**
199
+ * Get blocked users
200
+ */
201
+ export const useBlockedUsers = (options?: { enabled?: boolean }) => {
202
+ const { oxyServices, isAuthenticated } = useOxy();
203
+
204
+ return useQuery({
205
+ queryKey: queryKeys.privacy.blocked(),
206
+ queryFn: async () => {
207
+ return await oxyServices.getBlockedUsers();
208
+ },
209
+ enabled: (options?.enabled !== false) && isAuthenticated,
210
+ staleTime: 1 * 60 * 1000, // 1 minute
211
+ gcTime: 5 * 60 * 1000, // 5 minutes
212
+ });
213
+ };
214
+
215
+ /**
216
+ * Get restricted users
217
+ */
218
+ export const useRestrictedUsers = (options?: { enabled?: boolean }) => {
219
+ const { oxyServices, isAuthenticated } = useOxy();
220
+
221
+ return useQuery({
222
+ queryKey: queryKeys.privacy.restricted(),
223
+ queryFn: async () => {
224
+ return await oxyServices.getRestrictedUsers();
225
+ },
226
+ enabled: (options?.enabled !== false) && isAuthenticated,
227
+ staleTime: 1 * 60 * 1000, // 1 minute
228
+ gcTime: 5 * 60 * 1000, // 5 minutes
229
+ });
230
+ };
231
+
@@ -1,5 +1,4 @@
1
- import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
2
- import { useOxy } from '../context/OxyContext';
1
+ import { useQuery } from '@tanstack/react-query';
3
2
  import { useTransferStore } from '../stores/transferStore';
4
3
  import type { OxyServices } from '../../core';
5
4
 
@@ -7,66 +6,9 @@ import type { OxyServices } from '../../core';
7
6
  * Query keys for transfer-related queries
8
7
  */
9
8
  export const transferQueryKeys = {
10
- all: ['transfers'] as const,
11
- completion: (transferId: string) => ['transfers', 'completion', transferId] as const,
12
9
  pending: () => ['transfers', 'pending'] as const,
13
10
  };
14
11
 
15
- /**
16
- * Hook to check if a transfer was completed
17
- * Only runs when authenticated and transferId is provided
18
- */
19
- export const useCheckTransferCompletion = (transferId: string | null, enabled: boolean = true) => {
20
- const { oxyServices, isAuthenticated } = useOxy();
21
-
22
- return useQuery({
23
- queryKey: transferId ? transferQueryKeys.completion(transferId) : ['transfers', 'completion', 'null'],
24
- queryFn: async () => {
25
- if (!transferId || !oxyServices) {
26
- return null;
27
- }
28
-
29
- try {
30
- const response = await oxyServices.makeRequest<{
31
- completed: boolean;
32
- transferId?: string;
33
- sourceDeviceId?: string;
34
- publicKey?: string;
35
- transferCode?: string;
36
- completedAt?: string;
37
- }>(
38
- 'GET',
39
- `/api/identity/check-transfer/${transferId}`,
40
- undefined,
41
- { cache: false }
42
- );
43
-
44
- return response;
45
- } catch (error: any) {
46
- // Handle 401 errors gracefully - don't throw, just return null
47
- if (error?.status === 401 || error?.message?.includes('401') || error?.message?.includes('authentication')) {
48
- if (__DEV__) {
49
- console.warn('[useCheckTransferCompletion] Authentication required, skipping check');
50
- }
51
- return null;
52
- }
53
- throw error;
54
- }
55
- },
56
- enabled: enabled && !!transferId && isAuthenticated && !!oxyServices,
57
- staleTime: 30 * 1000, // 30 seconds - completion status doesn't change frequently
58
- gcTime: 5 * 60 * 1000, // 5 minutes
59
- retry: (failureCount, error: any) => {
60
- // Don't retry on 401 errors
61
- if (error?.status === 401 || error?.message?.includes('401')) {
62
- return false;
63
- }
64
- return failureCount < 2;
65
- },
66
- retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 5000),
67
- });
68
- };
69
-
70
12
  /**
71
13
  * Hook to check all pending transfers for completion
72
14
  * Used when app comes back online
@@ -158,11 +100,3 @@ export const useCheckPendingTransfers = (
158
100
  });
159
101
  };
160
102
 
161
- /**
162
- * Hook version that uses useOxy() - for use outside OxyContext
163
- */
164
- export const useCheckPendingTransfersWithContext = () => {
165
- const { oxyServices, isAuthenticated } = useOxy();
166
- return useCheckPendingTransfers(oxyServices, isAuthenticated);
167
- };
168
-
@@ -1,10 +1,9 @@
1
- import React, { useState, useCallback, useEffect, useMemo } from 'react';
1
+ import React, { useState, useCallback, useMemo } from 'react';
2
2
  import {
3
3
  View,
4
4
  Text,
5
5
  StyleSheet,
6
6
  ScrollView,
7
- ActivityIndicator,
8
7
  TouchableOpacity,
9
8
  } from 'react-native';
10
9
  import type { BaseScreenProps } from '../types/navigation';
@@ -15,6 +14,8 @@ import { useThemeStyles } from '../hooks/useThemeStyles';
15
14
  import { normalizeTheme } from '../utils/themeUtils';
16
15
  import type { BlockedUser, RestrictedUser } from '../../models/interfaces';
17
16
  import { useOxy } from '../context/OxyContext';
17
+ import { usePrivacySettings, useBlockedUsers, useRestrictedUsers } from '../hooks/queries';
18
+ import { useUpdatePrivacySettings, useUnblockUser, useUnrestrictUser } from '../hooks/mutations';
18
19
 
19
20
  interface PrivacySettings {
20
21
  isPrivateAccount: boolean;
@@ -44,8 +45,20 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
44
45
  goBack,
45
46
  }) => {
46
47
  // Use useOxy() hook for OxyContext values
47
- const { oxyServices, user } = useOxy();
48
+ const { oxyServices } = useOxy();
48
49
  const { t } = useI18n();
50
+
51
+ // TanStack Query hooks for server state
52
+ const { data: privacySettingsData, isLoading: isLoadingSettings, error: settingsError } = usePrivacySettings();
53
+ const { data: blockedUsers = [], isLoading: isLoadingBlocked } = useBlockedUsers();
54
+ const { data: restrictedUsers = [], isLoading: isLoadingRestricted } = useRestrictedUsers();
55
+
56
+ // Mutations
57
+ const updatePrivacySettingsMutation = useUpdatePrivacySettings();
58
+ const unblockUserMutation = useUnblockUser();
59
+ const unrestrictUserMutation = useUnrestrictUser();
60
+
61
+ // Client state for optimistic UI updates
49
62
  const [settings, setSettings] = useState<PrivacySettings>({
50
63
  isPrivateAccount: false,
51
64
  hideOnlineStatus: false,
@@ -67,111 +80,64 @@ const PrivacySettingsScreen: React.FC<BaseScreenProps> = ({
67
80
  autoFilter: true,
68
81
  muteKeywords: false,
69
82
  });
70
- const [isLoading, setIsLoading] = useState(true);
71
- const [isSaving, setIsSaving] = useState(false);
72
- const [blockedUsers, setBlockedUsers] = useState<BlockedUser[]>([]);
73
- const [restrictedUsers, setRestrictedUsers] = useState<RestrictedUser[]>([]);
74
- const [isLoadingUsers, setIsLoadingUsers] = useState(false);
75
-
76
- // Load settings and users
77
- useEffect(() => {
78
- const loadSettings = async () => {
79
- try {
80
- setIsLoading(true);
81
- // Use getCurrentUserId() which returns MongoDB ObjectId from JWT token
82
- // Never use user?.id as it may be set to publicKey
83
- const userId = oxyServices?.getCurrentUserId();
84
- if (userId && oxyServices) {
85
- const privacySettings = await oxyServices.getPrivacySettings(userId);
86
- if (privacySettings) {
87
- setSettings(privacySettings);
88
- }
89
- }
90
- } catch (error) {
91
- console.error('Failed to load privacy settings:', error);
92
- toast.error(t('privacySettings.loadError') || 'Failed to load privacy settings');
93
- } finally {
94
- setIsLoading(false);
95
- }
96
- };
97
-
98
- loadSettings();
99
- }, [oxyServices, t]);
100
-
101
- // Load blocked and restricted users
102
- useEffect(() => {
103
- const loadUsers = async () => {
104
- if (!oxyServices) return;
105
- try {
106
- setIsLoadingUsers(true);
107
- const [blocked, restricted] = await Promise.all([
108
- oxyServices.getBlockedUsers(),
109
- oxyServices.getRestrictedUsers(),
110
- ]);
111
- setBlockedUsers(blocked);
112
- setRestrictedUsers(restricted);
113
- } catch (error) {
114
- console.error('Failed to load blocked/restricted users:', error);
115
- } finally {
116
- setIsLoadingUsers(false);
117
- }
118
- };
119
-
120
- loadUsers();
121
- }, [oxyServices]);
83
+
84
+ // Update local state when server data changes
85
+ React.useEffect(() => {
86
+ if (privacySettingsData) {
87
+ setSettings(privacySettingsData as PrivacySettings);
88
+ }
89
+ }, [privacySettingsData]);
90
+
91
+ // Show error toast if settings failed to load
92
+ React.useEffect(() => {
93
+ if (settingsError) {
94
+ toast.error(t('privacySettings.loadError') || 'Failed to load privacy settings');
95
+ }
96
+ }, [settingsError, t]);
97
+
98
+ const isLoading = isLoadingSettings;
99
+ const isSaving = updatePrivacySettingsMutation.isPending;
100
+ const isLoadingUsers = isLoadingBlocked || isLoadingRestricted;
122
101
 
123
102
  const updateSetting = useCallback(async (key: keyof PrivacySettings, value: boolean) => {
124
- try {
125
- setIsSaving(true);
126
- const newSettings = { ...settings, [key]: value };
127
- setSettings(newSettings);
128
-
129
- // Use getCurrentUserId() which returns MongoDB ObjectId from JWT token
130
- // Never use user?.id as it may be set to publicKey
131
- const userId = oxyServices?.getCurrentUserId();
132
- if (userId && oxyServices) {
133
- await oxyServices.updatePrivacySettings({ [key]: value }, userId);
134
- toast.success(t('privacySettings.updated') || 'Privacy settings updated');
103
+ // Optimistic update
104
+ const newSettings = { ...settings, [key]: value };
105
+ setSettings(newSettings);
106
+
107
+ // Use mutation hook
108
+ updatePrivacySettingsMutation.mutate(
109
+ { settings: { [key]: value } },
110
+ {
111
+ onError: () => {
112
+ // Revert on error
113
+ setSettings(settings);
114
+ toast.error(t('privacySettings.updateError') || 'Failed to update privacy setting');
115
+ },
135
116
  }
136
- } catch (error) {
137
- console.error(`Failed to update ${key}:`, error);
138
- toast.error(t('privacySettings.updateError') || 'Failed to update privacy setting');
139
- // Revert on error
140
- setSettings(settings);
141
- } finally {
142
- setIsSaving(false);
143
- }
144
- }, [settings, oxyServices, t]);
117
+ );
118
+ }, [settings, updatePrivacySettingsMutation, t]);
145
119
 
146
120
  const handleUnblock = useCallback(async (userId: string) => {
147
- if (!oxyServices) return;
148
- try {
149
- await oxyServices.unblockUser(userId);
150
- setBlockedUsers(prev => prev.filter(u => {
151
- const id = typeof u.blockedId === 'string' ? u.blockedId : u.blockedId._id;
152
- return id !== userId;
153
- }));
154
- toast.success(t('privacySettings.userUnblocked') || 'User unblocked');
155
- } catch (error) {
156
- console.error('Failed to unblock user:', error);
157
- toast.error(t('privacySettings.unblockError') || 'Failed to unblock user');
158
- }
159
- }, [oxyServices, t]);
121
+ unblockUserMutation.mutate(userId, {
122
+ onSuccess: () => {
123
+ toast.success(t('privacySettings.userUnblocked') || 'User unblocked');
124
+ },
125
+ onError: () => {
126
+ toast.error(t('privacySettings.unblockError') || 'Failed to unblock user');
127
+ },
128
+ });
129
+ }, [unblockUserMutation, t]);
160
130
 
161
131
  const handleUnrestrict = useCallback(async (userId: string) => {
162
- if (!oxyServices) return;
163
- try {
164
- await oxyServices.unrestrictUser(userId);
165
- setRestrictedUsers(prev => prev.filter(u => {
166
- const id = typeof u.restrictedId === 'string' ? u.restrictedId : u.restrictedId._id;
167
- return id !== userId;
168
- }));
169
- toast.success(t('privacySettings.userUnrestricted') || 'User unrestricted');
170
- } catch (error) {
171
- console.error('Failed to unrestrict user:', error);
172
- toast.error(t('privacySettings.unrestrictError') || 'Failed to unrestrict user');
173
- }
174
- }, [oxyServices, t]);
132
+ unrestrictUserMutation.mutate(userId, {
133
+ onSuccess: () => {
134
+ toast.success(t('privacySettings.userUnrestricted') || 'User unrestricted');
135
+ },
136
+ onError: () => {
137
+ toast.error(t('privacySettings.unrestrictError') || 'Failed to unrestrict user');
138
+ },
139
+ });
140
+ }, [unrestrictUserMutation, t]);
175
141
 
176
142
  // Helper to extract user info from blocked/restricted objects
177
143
  const extractUserInfo = useCallback((
@@ -192,10 +192,4 @@ export const useTransferCodesForPersistence = () => {
192
192
  );
193
193
  };
194
194
 
195
- /**
196
- * Hook to check if store has been restored
197
- */
198
- export const useTransferStoreRestored = () => {
199
- return useTransferStore((state) => state.isRestored);
200
- };
201
195