@oxyhq/services 5.16.7 → 5.16.8
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/ui/context/OxyContext.js +50 -2
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js +6 -6
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +3 -3
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/utils/fileManagement.js +88 -0
- package/lib/commonjs/ui/utils/fileManagement.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +50 -2
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/screens/AccountOverviewScreen.js +6 -6
- package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +3 -3
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/utils/fileManagement.js +87 -0
- package/lib/module/ui/utils/fileManagement.js.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts +1 -0
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/utils/fileManagement.d.ts +48 -0
- package/lib/typescript/ui/utils/fileManagement.d.ts.map +1 -1
- package/package.json +6 -2
- package/src/ui/context/OxyContext.tsx +49 -0
- package/src/ui/screens/AccountOverviewScreen.tsx +4 -3
- package/src/ui/screens/AccountSettingsScreen.tsx +3 -5
- package/src/ui/screens/FileManagementScreen.tsx +1 -1
- package/src/ui/utils/fileManagement.ts +105 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxyhq/services",
|
|
3
|
-
"version": "5.16.
|
|
3
|
+
"version": "5.16.8",
|
|
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",
|
|
@@ -161,7 +161,8 @@
|
|
|
161
161
|
"react-native-gesture-handler": "~2.28.0",
|
|
162
162
|
"react-native-reanimated": "~4.1.1",
|
|
163
163
|
"react-native-safe-area-context": "~5.6.0",
|
|
164
|
-
"react-native-svg": ">=13.0.0"
|
|
164
|
+
"react-native-svg": ">=13.0.0",
|
|
165
|
+
"expo-document-picker": "~14.0.0"
|
|
165
166
|
},
|
|
166
167
|
"peerDependenciesMeta": {
|
|
167
168
|
"@expo/vector-icons": {
|
|
@@ -190,6 +191,9 @@
|
|
|
190
191
|
},
|
|
191
192
|
"@react-navigation/native": {
|
|
192
193
|
"optional": true
|
|
194
|
+
},
|
|
195
|
+
"expo-document-picker": {
|
|
196
|
+
"optional": true
|
|
193
197
|
}
|
|
194
198
|
},
|
|
195
199
|
"react-native-builder-bob": {
|
|
@@ -30,6 +30,9 @@ import { useQueryClient } from '@tanstack/react-query';
|
|
|
30
30
|
import { clearQueryCache } from '../hooks/queryClient';
|
|
31
31
|
import { useAccountStore } from '../stores/accountStore';
|
|
32
32
|
import { KeyManager } from '../../crypto/keyManager';
|
|
33
|
+
import { translate } from '../../i18n';
|
|
34
|
+
import { queryKeys } from '../hooks/queries/queryKeys';
|
|
35
|
+
import { useUpdateProfile } from '../hooks/mutations/useAccountMutations';
|
|
33
36
|
|
|
34
37
|
export interface OxyContextState {
|
|
35
38
|
user: User | null;
|
|
@@ -84,6 +87,7 @@ export interface OxyContextState {
|
|
|
84
87
|
oxyServices: OxyServices;
|
|
85
88
|
useFollow?: UseFollowHook;
|
|
86
89
|
showBottomSheet?: (screenOrConfig: RouteName | { screen: RouteName; props?: Record<string, unknown> }) => void;
|
|
90
|
+
openAvatarPicker: () => void;
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
const OxyContext = createContext<OxyContextState | null>(null);
|
|
@@ -482,6 +486,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
482
486
|
|
|
483
487
|
const useFollowHook = loadUseFollowHook();
|
|
484
488
|
|
|
489
|
+
// Create update profile mutation for avatar picker
|
|
490
|
+
const updateProfileMutation = useUpdateProfile();
|
|
491
|
+
|
|
485
492
|
const restoreSessionsFromStorage = useCallback(async (): Promise<void> => {
|
|
486
493
|
if (!storage) {
|
|
487
494
|
return;
|
|
@@ -615,6 +622,46 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
615
622
|
[],
|
|
616
623
|
);
|
|
617
624
|
|
|
625
|
+
// Create openAvatarPicker function
|
|
626
|
+
const openAvatarPicker = useCallback(() => {
|
|
627
|
+
showBottomSheetForContext({
|
|
628
|
+
screen: 'FileManagement' as RouteName,
|
|
629
|
+
props: {
|
|
630
|
+
selectMode: true,
|
|
631
|
+
multiSelect: false,
|
|
632
|
+
disabledMimeTypes: ['video/', 'audio/', 'application/pdf'],
|
|
633
|
+
afterSelect: 'none', // Don't navigate away - stay on current screen
|
|
634
|
+
onSelect: async (file: any) => {
|
|
635
|
+
if (!file.contentType.startsWith('image/')) {
|
|
636
|
+
toast.error(translate(currentLanguage, 'editProfile.toasts.selectImage') || 'Please select an image file');
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
try {
|
|
640
|
+
// Update file visibility to public for avatar (skip if temporary asset ID)
|
|
641
|
+
if (file.id && !file.id.startsWith('temp-')) {
|
|
642
|
+
try {
|
|
643
|
+
await oxyServices.assetUpdateVisibility(file.id, 'public');
|
|
644
|
+
console.log('[OxyContext] Avatar visibility updated to public');
|
|
645
|
+
} catch (visError: any) {
|
|
646
|
+
// Only log non-404 errors (404 means asset doesn't exist yet, which is OK)
|
|
647
|
+
if (visError?.response?.status !== 404) {
|
|
648
|
+
console.warn('[OxyContext] Failed to update avatar visibility, continuing anyway:', visError);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Update user profile using mutation hook (provides optimistic updates, error handling, retry)
|
|
654
|
+
await updateProfileMutation.mutateAsync({ avatar: file.id });
|
|
655
|
+
|
|
656
|
+
toast.success(translate(currentLanguage, 'editProfile.toasts.avatarUpdated') || 'Avatar updated');
|
|
657
|
+
} catch (e: any) {
|
|
658
|
+
toast.error(e.message || translate(currentLanguage, 'editProfile.toasts.updateAvatarFailed') || 'Failed to update avatar');
|
|
659
|
+
}
|
|
660
|
+
},
|
|
661
|
+
},
|
|
662
|
+
});
|
|
663
|
+
}, [oxyServices, currentLanguage, showBottomSheetForContext, updateProfileMutation]);
|
|
664
|
+
|
|
618
665
|
const contextValue: OxyContextState = useMemo(() => ({
|
|
619
666
|
user,
|
|
620
667
|
sessions,
|
|
@@ -654,6 +701,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
654
701
|
oxyServices,
|
|
655
702
|
useFollow: useFollowHook,
|
|
656
703
|
showBottomSheet: showBottomSheetForContext,
|
|
704
|
+
openAvatarPicker,
|
|
657
705
|
}), [
|
|
658
706
|
activeSessionId,
|
|
659
707
|
createIdentity,
|
|
@@ -689,6 +737,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
689
737
|
useFollowHook,
|
|
690
738
|
user,
|
|
691
739
|
showBottomSheetForContext,
|
|
740
|
+
openAvatarPicker,
|
|
692
741
|
]);
|
|
693
742
|
|
|
694
743
|
return (
|
|
@@ -108,10 +108,11 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
|
|
|
108
108
|
return undefined;
|
|
109
109
|
}, [user?.avatar, oxyServices]);
|
|
110
110
|
|
|
111
|
-
// Handle avatar press
|
|
111
|
+
// Handle avatar press - use openAvatarPicker from context
|
|
112
|
+
const { openAvatarPicker } = useOxy();
|
|
112
113
|
const handleAvatarPress = useCallback(() => {
|
|
113
|
-
|
|
114
|
-
}, [
|
|
114
|
+
openAvatarPicker();
|
|
115
|
+
}, [openAvatarPicker]);
|
|
115
116
|
|
|
116
117
|
// Play Lottie animation once when component mounts
|
|
117
118
|
useEffect(() => {
|
|
@@ -75,12 +75,12 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
|
|
|
75
75
|
} = useOxy();
|
|
76
76
|
const { t } = useI18n();
|
|
77
77
|
const normalizedTheme = normalizeTheme(theme);
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
// Use TanStack Query for user data
|
|
80
80
|
const { data: user, isLoading: userLoading } = useCurrentUser({ enabled: isAuthenticated });
|
|
81
81
|
const updateProfileMutation = useUpdateProfile();
|
|
82
82
|
const uploadAvatarMutation = useUploadAvatar();
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
// Fallback to store for backward compatibility
|
|
85
85
|
const userFromStore = useAuthStore((state) => state.user);
|
|
86
86
|
const finalUser = user || userFromStore;
|
|
@@ -503,9 +503,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string;
|
|
|
503
503
|
});
|
|
504
504
|
};
|
|
505
505
|
|
|
506
|
-
const openAvatarPicker =
|
|
507
|
-
toast.info?.(t('editProfile.toasts.avatarPickerUnavailable') || 'Avatar picker is not available in this build.');
|
|
508
|
-
}, [t]);
|
|
506
|
+
const { openAvatarPicker } = useOxy();
|
|
509
507
|
|
|
510
508
|
// Handlers to open modals
|
|
511
509
|
const handleOpenDisplayNameModal = useCallback(() => setShowEditDisplayNameModal(true), []);
|
|
@@ -769,7 +769,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
769
769
|
|
|
770
770
|
try {
|
|
771
771
|
setIsPickingDocument(true);
|
|
772
|
-
|
|
772
|
+
|
|
773
773
|
// Use expo-document-picker (works on all platforms including web)
|
|
774
774
|
// On web, it uses the native file input and provides File objects directly
|
|
775
775
|
const result = await DocumentPicker.getDocumentAsync({
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Alert } from 'react-native';
|
|
2
2
|
import type { FileMetadata } from '../../models/interfaces';
|
|
3
3
|
import { File as ExpoFile } from 'expo-file-system';
|
|
4
|
+
import { toast } from '../../lib/sonner';
|
|
5
|
+
import type { RouteName } from '../navigation/routes';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Format file size in bytes to human-readable string
|
|
@@ -188,3 +190,106 @@ export async function uploadFileRaw(
|
|
|
188
190
|
return await oxyServices.uploadRawFile(file, visibility);
|
|
189
191
|
}
|
|
190
192
|
|
|
193
|
+
/**
|
|
194
|
+
* Configuration for creating an avatar picker handler
|
|
195
|
+
*/
|
|
196
|
+
export interface AvatarPickerConfig {
|
|
197
|
+
/** Navigation function from BaseScreenProps */
|
|
198
|
+
navigate?: (screen: RouteName, props?: Record<string, unknown>) => void;
|
|
199
|
+
/** OxyServices instance */
|
|
200
|
+
oxyServices: any;
|
|
201
|
+
/** TanStack Query mutation for updating profile */
|
|
202
|
+
updateProfileMutation: {
|
|
203
|
+
mutateAsync: (updates: { avatar: string }) => Promise<any>;
|
|
204
|
+
};
|
|
205
|
+
/** Callback to update local avatar state */
|
|
206
|
+
onAvatarSelected?: (fileId: string) => void;
|
|
207
|
+
/** i18n translation function */
|
|
208
|
+
t: (key: string) => string | undefined;
|
|
209
|
+
/** Optional context name for logging (e.g., 'AccountSettings', 'WelcomeNewUser') */
|
|
210
|
+
contextName?: string;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Creates a reusable avatar picker handler function.
|
|
215
|
+
*
|
|
216
|
+
* This function navigates to the FileManagement screen and handles:
|
|
217
|
+
* - Image file validation
|
|
218
|
+
* - File visibility update to public
|
|
219
|
+
* - Profile avatar update via mutation
|
|
220
|
+
* - Success/error toast notifications
|
|
221
|
+
*
|
|
222
|
+
* @example
|
|
223
|
+
* ```tsx
|
|
224
|
+
* const openAvatarPicker = createAvatarPickerHandler({
|
|
225
|
+
* navigate,
|
|
226
|
+
* oxyServices,
|
|
227
|
+
* updateProfileMutation,
|
|
228
|
+
* onAvatarSelected: setAvatarFileId,
|
|
229
|
+
* t,
|
|
230
|
+
* contextName: 'AccountSettings'
|
|
231
|
+
* });
|
|
232
|
+
*
|
|
233
|
+
* <TouchableOpacity onPress={openAvatarPicker}>
|
|
234
|
+
* <Text>Change Avatar</Text>
|
|
235
|
+
* </TouchableOpacity>
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
export function createAvatarPickerHandler(config: AvatarPickerConfig): () => void {
|
|
239
|
+
const {
|
|
240
|
+
navigate,
|
|
241
|
+
oxyServices,
|
|
242
|
+
updateProfileMutation,
|
|
243
|
+
onAvatarSelected,
|
|
244
|
+
t,
|
|
245
|
+
contextName = 'AvatarPicker'
|
|
246
|
+
} = config;
|
|
247
|
+
|
|
248
|
+
return () => {
|
|
249
|
+
if (!navigate) {
|
|
250
|
+
console.warn(`[${contextName}] navigate function is not available`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
navigate('FileManagement', {
|
|
255
|
+
selectMode: true,
|
|
256
|
+
multiSelect: false,
|
|
257
|
+
disabledMimeTypes: ['video/', 'audio/', 'application/pdf'],
|
|
258
|
+
afterSelect: 'none', // Don't navigate away - stay on current screen
|
|
259
|
+
onSelect: async (file: any) => {
|
|
260
|
+
if (!file.contentType.startsWith('image/')) {
|
|
261
|
+
toast.error(t('editProfile.toasts.selectImage') || 'Please select an image file');
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
// Update file visibility to public for avatar (skip if temporary asset ID)
|
|
267
|
+
if (file.id && !file.id.startsWith('temp-')) {
|
|
268
|
+
try {
|
|
269
|
+
await oxyServices.assetUpdateVisibility(file.id, 'public');
|
|
270
|
+
console.log(`[${contextName}] Avatar visibility updated to public`);
|
|
271
|
+
} catch (visError: any) {
|
|
272
|
+
// Only log non-404 errors (404 means asset doesn't exist yet, which is OK)
|
|
273
|
+
if (visError?.response?.status !== 404) {
|
|
274
|
+
console.warn(`[${contextName}] Failed to update avatar visibility, continuing anyway:`, visError);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Update local state if callback provided
|
|
280
|
+
if (onAvatarSelected) {
|
|
281
|
+
onAvatarSelected(file.id);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Update user using TanStack Query mutation
|
|
285
|
+
await updateProfileMutation.mutateAsync({ avatar: file.id });
|
|
286
|
+
|
|
287
|
+
toast.success(t('editProfile.toasts.avatarUpdated') || 'Avatar updated');
|
|
288
|
+
} catch (e: any) {
|
|
289
|
+
toast.error(e.message || t('editProfile.toasts.updateAvatarFailed') || 'Failed to update avatar');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|