@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.
- package/lib/commonjs/crypto/keyManager.js +3 -0
- package/lib/commonjs/crypto/keyManager.js.map +1 -1
- package/lib/commonjs/index.js +64 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +70 -17
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/auth/index.js +37 -0
- package/lib/commonjs/ui/hooks/auth/index.js.map +1 -0
- package/lib/commonjs/ui/hooks/auth/useUsernameValidation.js +171 -0
- package/lib/commonjs/ui/hooks/auth/useUsernameValidation.js.map +1 -0
- package/lib/commonjs/ui/hooks/index.js +20 -0
- package/lib/commonjs/ui/hooks/index.js.map +1 -1
- package/lib/commonjs/ui/hooks/mutations/index.js +12 -0
- package/lib/commonjs/ui/hooks/mutations/index.js.map +1 -1
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +45 -1
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/index.js +12 -0
- package/lib/commonjs/ui/hooks/queries/index.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/queryKeys.js +3 -1
- package/lib/commonjs/ui/hooks/queries/queryKeys.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +43 -1
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/commonjs/ui/hooks/useTransferQueries.js +1 -64
- package/lib/commonjs/ui/hooks/useTransferQueries.js.map +1 -1
- package/lib/commonjs/ui/screens/PrivacySettingsScreen.js +76 -97
- package/lib/commonjs/ui/screens/PrivacySettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/stores/transferStore.js +1 -9
- package/lib/commonjs/ui/stores/transferStore.js.map +1 -1
- package/lib/module/crypto/keyManager.js +3 -0
- package/lib/module/crypto/keyManager.js.map +1 -1
- package/lib/module/index.js +3 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +70 -17
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/auth/index.js +7 -0
- package/lib/module/ui/hooks/auth/index.js.map +1 -0
- package/lib/module/ui/hooks/auth/useUsernameValidation.js +167 -0
- package/lib/module/ui/hooks/auth/useUsernameValidation.js.map +1 -0
- package/lib/module/ui/hooks/index.js +1 -0
- package/lib/module/ui/hooks/index.js.map +1 -1
- package/lib/module/ui/hooks/mutations/index.js +1 -1
- package/lib/module/ui/hooks/mutations/index.js.map +1 -1
- package/lib/module/ui/hooks/mutations/useAccountMutations.js +42 -0
- package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/module/ui/hooks/queries/index.js +1 -1
- package/lib/module/ui/hooks/queries/index.js.map +1 -1
- package/lib/module/ui/hooks/queries/queryKeys.js +3 -1
- package/lib/module/ui/hooks/queries/queryKeys.js.map +1 -1
- package/lib/module/ui/hooks/queries/useAccountQueries.js +40 -0
- package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/module/ui/hooks/useTransferQueries.js +0 -61
- package/lib/module/ui/hooks/useTransferQueries.js.map +1 -1
- package/lib/module/ui/screens/PrivacySettingsScreen.js +77 -98
- package/lib/module/ui/screens/PrivacySettingsScreen.js.map +1 -1
- package/lib/module/ui/stores/transferStore.js +0 -7
- package/lib/module/ui/stores/transferStore.js.map +1 -1
- package/lib/typescript/crypto/keyManager.d.ts +2 -1
- package/lib/typescript/crypto/keyManager.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +4 -2
- 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/auth/index.d.ts +6 -0
- package/lib/typescript/ui/hooks/auth/index.d.ts.map +1 -0
- package/lib/typescript/ui/hooks/auth/useUsernameValidation.d.ts +32 -0
- package/lib/typescript/ui/hooks/auth/useUsernameValidation.d.ts.map +1 -0
- package/lib/typescript/ui/hooks/index.d.ts +1 -0
- package/lib/typescript/ui/hooks/index.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/mutations/index.d.ts +1 -1
- package/lib/typescript/ui/hooks/mutations/index.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts +12 -0
- package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/queries/index.d.ts +1 -1
- package/lib/typescript/ui/hooks/queries/index.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/queries/queryKeys.d.ts +2 -0
- package/lib/typescript/ui/hooks/queries/queryKeys.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts +12 -0
- package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useTransferQueries.d.ts +0 -28
- package/lib/typescript/ui/hooks/useTransferQueries.d.ts.map +1 -1
- package/lib/typescript/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/stores/transferStore.d.ts +0 -4
- package/lib/typescript/ui/stores/transferStore.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/crypto/keyManager.ts +5 -1
- package/src/index.ts +6 -0
- package/src/ui/context/OxyContext.tsx +67 -13
- package/src/ui/hooks/auth/index.ts +6 -0
- package/src/ui/hooks/auth/useUsernameValidation.ts +177 -0
- package/src/ui/hooks/index.ts +2 -1
- package/src/ui/hooks/mutations/index.ts +2 -0
- package/src/ui/hooks/mutations/useAccountMutations.ts +36 -0
- package/src/ui/hooks/queries/index.ts +2 -0
- package/src/ui/hooks/queries/queryKeys.ts +2 -0
- package/src/ui/hooks/queries/useAccountQueries.ts +34 -0
- package/src/ui/hooks/useTransferQueries.ts +1 -67
- package/src/ui/screens/PrivacySettingsScreen.tsx +67 -101
- package/src/ui/stores/transferStore.ts +0 -6
- package/lib/commonjs/ui/context/hooks/useSessionManagement.js +0 -281
- package/lib/commonjs/ui/context/hooks/useSessionManagement.js.map +0 -1
- package/lib/commonjs/ui/context/hooks/useStorage.js +0 -79
- package/lib/commonjs/ui/context/hooks/useStorage.js.map +0 -1
- package/lib/module/ui/context/hooks/useSessionManagement.js +0 -276
- package/lib/module/ui/context/hooks/useSessionManagement.js.map +0 -1
- package/lib/module/ui/context/hooks/useStorage.js +0 -74
- package/lib/module/ui/context/hooks/useStorage.js.map +0 -1
- package/lib/typescript/ui/context/hooks/useSessionManagement.d.ts +0 -41
- package/lib/typescript/ui/context/hooks/useSessionManagement.d.ts.map +0 -1
- package/lib/typescript/ui/context/hooks/useStorage.d.ts +0 -22
- package/lib/typescript/ui/context/hooks/useStorage.d.ts.map +0 -1
- package/src/ui/context/hooks/useSessionManagement.ts +0 -401
- 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
|
+
|
package/src/ui/hooks/index.ts
CHANGED
|
@@ -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';
|
|
@@ -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
|
+
|
|
@@ -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
|
|
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,
|
|
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
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
137
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|