@oxyhq/services 5.11.9 → 5.11.10
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/components/AnimationExample.js +213 -0
- package/lib/commonjs/ui/components/AnimationExample.js.map +1 -0
- package/lib/commonjs/ui/components/FollowButton.js +58 -47
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/components/GroupedItem.js +2 -1
- package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
- package/lib/commonjs/ui/components/GroupedSection.js +3 -0
- package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
- package/lib/commonjs/ui/components/Header.js +25 -11
- package/lib/commonjs/ui/components/Header.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +69 -33
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileCard.js +5 -1
- package/lib/commonjs/ui/components/ProfileCard.js.map +1 -1
- package/lib/commonjs/ui/components/index.js +0 -7
- package/lib/commonjs/ui/components/index.js.map +1 -1
- package/lib/commonjs/ui/components/internal/TextField.js +8 -4
- package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
- package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +161 -0
- package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +97 -38
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/useFollow.types.js +2 -0
- package/lib/commonjs/ui/hooks/useFollow.types.js.map +1 -0
- package/lib/commonjs/ui/navigation/OxyRouter.js +10 -0
- package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountCenterScreen.js +26 -14
- package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js +3 -3
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +64 -15
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +4 -4
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FeedbackScreen.js +72 -75
- package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +286 -126
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +322 -0
- package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/ProfileScreen.js +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SessionManagementScreen.js +176 -174
- package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js +43 -52
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignUpScreen.js +6 -4
- package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +386 -0
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +25 -15
- package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +16 -9
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/styles/authStyles.js +1 -1
- package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
- package/lib/module/ui/components/AnimationExample.js +209 -0
- package/lib/module/ui/components/AnimationExample.js.map +1 -0
- package/lib/module/ui/components/FollowButton.js +58 -47
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/GroupedItem.js +2 -1
- package/lib/module/ui/components/GroupedItem.js.map +1 -1
- package/lib/module/ui/components/GroupedSection.js +3 -0
- package/lib/module/ui/components/GroupedSection.js.map +1 -1
- package/lib/module/ui/components/Header.js +25 -11
- package/lib/module/ui/components/Header.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +70 -34
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/ProfileCard.js +5 -1
- package/lib/module/ui/components/ProfileCard.js.map +1 -1
- package/lib/module/ui/components/index.js +0 -1
- package/lib/module/ui/components/index.js.map +1 -1
- package/lib/module/ui/components/internal/TextField.js +8 -4
- package/lib/module/ui/components/internal/TextField.js.map +1 -1
- package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +156 -0
- package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +97 -39
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/useFollow.types.js +2 -0
- package/lib/module/ui/hooks/useFollow.types.js.map +1 -0
- package/lib/module/ui/navigation/OxyRouter.js +10 -0
- package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/module/ui/screens/AccountCenterScreen.js +12 -1
- package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountOverviewScreen.js +3 -3
- package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +64 -15
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +4 -4
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/FeedbackScreen.js +72 -75
- package/lib/module/ui/screens/FeedbackScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +285 -125
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/LanguageSelectorScreen.js +319 -0
- package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -0
- package/lib/module/ui/screens/ProfileScreen.js +1 -1
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/SessionManagementScreen.js +177 -175
- package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +44 -53
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/SignUpScreen.js +6 -4
- package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +382 -0
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -0
- package/lib/module/ui/screens/internal/SignInPasswordStep.js +23 -14
- package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/internal/SignInUsernameStep.js +15 -9
- package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js +1 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/module/ui/styles/authStyles.js +1 -1
- package/lib/module/ui/styles/authStyles.js.map +1 -1
- package/lib/typescript/models/interfaces.d.ts +1 -5
- package/lib/typescript/models/interfaces.d.ts.map +1 -1
- package/lib/typescript/models/session.d.ts +1 -4
- package/lib/typescript/models/session.d.ts.map +1 -1
- package/lib/typescript/ui/components/AnimationExample.d.ts +4 -0
- package/lib/typescript/ui/components/AnimationExample.d.ts.map +1 -0
- package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
- package/lib/typescript/ui/components/Header.d.ts +9 -0
- package/lib/typescript/ui/components/Header.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/ui/components/ProfileCard.d.ts +1 -3
- package/lib/typescript/ui/components/ProfileCard.d.ts.map +1 -1
- package/lib/typescript/ui/components/index.d.ts +0 -1
- package/lib/typescript/ui/components/index.d.ts.map +1 -1
- package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
- package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts +27 -0
- package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -0
- package/lib/typescript/ui/context/OxyContext.d.ts +6 -2
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useFollow.types.d.ts +33 -0
- package/lib/typescript/ui/hooks/useFollow.types.d.ts.map +1 -0
- package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
- package/lib/typescript/ui/navigation/types.d.ts +5 -0
- package/lib/typescript/ui/navigation/types.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/FeedbackScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/FileManagementScreen.d.ts +18 -1
- package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +7 -0
- package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts +13 -0
- package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts +5 -5
- package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts +4 -4
- package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts.map +1 -1
- package/lib/typescript/ui/styles/authStyles.d.ts +1 -1
- package/package.json +10 -2
- package/src/models/interfaces.ts +2 -5
- package/src/models/session.ts +1 -4
- package/src/ui/components/AnimationExample.tsx +194 -0
- package/src/ui/components/FollowButton.tsx +65 -45
- package/src/ui/components/GroupedItem.tsx +1 -0
- package/src/ui/components/GroupedSection.tsx +1 -1
- package/src/ui/components/Header.tsx +36 -12
- package/src/ui/components/OxyProvider.tsx +66 -32
- package/src/ui/components/ProfileCard.tsx +6 -8
- package/src/ui/components/index.ts +0 -1
- package/src/ui/components/internal/TextField.tsx +12 -6
- package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +158 -0
- package/src/ui/context/OxyContext.tsx +84 -54
- package/src/ui/hooks/useFollow.types.ts +33 -0
- package/src/ui/navigation/OxyRouter.tsx +10 -0
- package/src/ui/navigation/types.ts +6 -0
- package/src/ui/screens/AccountCenterScreen.tsx +13 -7
- package/src/ui/screens/AccountOverviewScreen.tsx +3 -3
- package/src/ui/screens/AccountSettingsScreen.tsx +65 -13
- package/src/ui/screens/AccountSwitcherScreen.tsx +4 -4
- package/src/ui/screens/FeedbackScreen.tsx +57 -80
- package/src/ui/screens/FileManagementScreen.tsx +278 -175
- package/src/ui/screens/LanguageSelectorScreen.tsx +322 -0
- package/src/ui/screens/ProfileScreen.tsx +6 -1
- package/src/ui/screens/SessionManagementScreen.tsx +148 -151
- package/src/ui/screens/SignInScreen.tsx +43 -62
- package/src/ui/screens/SignUpScreen.tsx +3 -5
- package/src/ui/screens/WelcomeNewUserScreen.tsx +272 -0
- package/src/ui/screens/internal/SignInPasswordStep.tsx +28 -13
- package/src/ui/screens/internal/SignInUsernameStep.tsx +21 -11
- package/src/ui/screens/karma/KarmaCenterScreen.tsx +1 -1
- package/src/ui/styles/authStyles.ts +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { createContext, useContext, useEffect, useCallback, type ReactNode, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import type { UseFollowHook } from '../hooks/useFollow.types';
|
|
2
3
|
import { View, Text } from 'react-native';
|
|
3
4
|
import { OxyServices } from '../../core';
|
|
4
5
|
import type { User, ApiError } from '../../models/interfaces';
|
|
@@ -9,8 +10,8 @@ import { toast } from '../../lib/sonner';
|
|
|
9
10
|
import { useAuthStore } from '../stores/authStore';
|
|
10
11
|
|
|
11
12
|
// Define the context shape
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
// NOTE: We intentionally avoid importing useFollow here to prevent a require cycle.
|
|
14
|
+
// If consumers relied on `const { useFollow } = useOxy()`, we provide a lazy proxy below.
|
|
14
15
|
|
|
15
16
|
export interface OxyContextState {
|
|
16
17
|
// Authentication state
|
|
@@ -22,6 +23,9 @@ export interface OxyContextState {
|
|
|
22
23
|
isLoading: boolean;
|
|
23
24
|
error: string | null;
|
|
24
25
|
|
|
26
|
+
// Language state
|
|
27
|
+
currentLanguage: string;
|
|
28
|
+
|
|
25
29
|
// Auth methods
|
|
26
30
|
login: (username: string, password: string, deviceName?: string) => Promise<User>;
|
|
27
31
|
logout: (targetSessionId?: string) => Promise<void>;
|
|
@@ -33,6 +37,9 @@ export interface OxyContextState {
|
|
|
33
37
|
removeSession: (sessionId: string) => Promise<void>;
|
|
34
38
|
refreshSessions: () => Promise<void>;
|
|
35
39
|
|
|
40
|
+
// Language methods
|
|
41
|
+
setLanguage: (languageId: string) => Promise<void>;
|
|
42
|
+
|
|
36
43
|
// Device management methods
|
|
37
44
|
getDeviceSessions: () => Promise<any[]>;
|
|
38
45
|
logoutAllDeviceSessions: () => Promise<void>;
|
|
@@ -47,9 +54,10 @@ export interface OxyContextState {
|
|
|
47
54
|
hideBottomSheet?: () => void;
|
|
48
55
|
|
|
49
56
|
/**
|
|
50
|
-
* useFollow hook
|
|
57
|
+
* (Deprecated) useFollow hook access via context. Prefer: import { useFollow } from '@oxyhq/services';
|
|
58
|
+
* Kept for backward compatibility; implemented as a lazy dynamic require to avoid circular dependency.
|
|
51
59
|
*/
|
|
52
|
-
useFollow:
|
|
60
|
+
useFollow: UseFollowHook; // Back-compat; prefer direct import
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
// Create the context with default values
|
|
@@ -122,6 +130,7 @@ const getStorage = async (): Promise<StorageInterface> => {
|
|
|
122
130
|
// Storage keys for sessions
|
|
123
131
|
const getStorageKeys = (prefix = 'oxy_session') => ({
|
|
124
132
|
activeSessionId: `${prefix}_active_session_id`, // Only store the active session ID
|
|
133
|
+
language: `${prefix}_language`, // Store the selected language
|
|
125
134
|
});
|
|
126
135
|
|
|
127
136
|
export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
@@ -162,6 +171,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
162
171
|
const [sessions, setSessions] = React.useState<ClientSession[]>([]);
|
|
163
172
|
const [activeSessionId, setActiveSessionId] = React.useState<string | null>(null);
|
|
164
173
|
const [storage, setStorage] = React.useState<StorageInterface | null>(null);
|
|
174
|
+
const [currentLanguage, setCurrentLanguage] = React.useState<string>('en');
|
|
165
175
|
// Add a new state to track token restoration
|
|
166
176
|
const [tokenReady, setTokenReady] = React.useState(false);
|
|
167
177
|
|
|
@@ -185,88 +195,66 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
185
195
|
const platformStorage = await getStorage();
|
|
186
196
|
setStorage(platformStorage);
|
|
187
197
|
} catch (error) {
|
|
188
|
-
console.error('
|
|
198
|
+
console.error('Init storage failed', error);
|
|
189
199
|
useAuthStore.setState({ error: 'Failed to initialize storage' });
|
|
190
200
|
}
|
|
191
201
|
};
|
|
192
|
-
|
|
193
202
|
initStorage();
|
|
194
203
|
}, []);
|
|
195
204
|
|
|
196
|
-
//
|
|
205
|
+
// Initialize authentication state
|
|
197
206
|
useEffect(() => {
|
|
198
207
|
const initAuth = async () => {
|
|
199
208
|
if (!storage) return;
|
|
200
|
-
|
|
201
209
|
useAuthStore.setState({ isLoading: true });
|
|
202
210
|
try {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
211
|
+
setTokenReady(false);
|
|
212
|
+
|
|
213
|
+
// Load saved language preference
|
|
214
|
+
const savedLanguage = await storage.getItem(keys.language);
|
|
215
|
+
if (savedLanguage) {
|
|
216
|
+
setCurrentLanguage(savedLanguage);
|
|
217
|
+
}
|
|
209
218
|
|
|
219
|
+
// Try to restore active session from storage
|
|
220
|
+
const storedActiveSessionId = await storage.getItem(keys.activeSessionId);
|
|
210
221
|
if (storedActiveSessionId) {
|
|
211
|
-
// Validate the stored session with the backend
|
|
212
222
|
try {
|
|
213
|
-
const validation = await oxyServices.validateSession(storedActiveSessionId, {
|
|
214
|
-
useHeaderValidation: true
|
|
215
|
-
});
|
|
216
|
-
|
|
223
|
+
const validation = await oxyServices.validateSession(storedActiveSessionId, { useHeaderValidation: true });
|
|
217
224
|
if (validation.valid) {
|
|
218
|
-
console.log('Auth - session validated successfully');
|
|
219
225
|
setActiveSessionId(storedActiveSessionId);
|
|
220
|
-
|
|
221
|
-
// Get access token for API calls
|
|
222
226
|
await oxyServices.getTokenBySession(storedActiveSessionId);
|
|
223
|
-
|
|
224
|
-
// Load full user data from backend
|
|
225
227
|
const fullUser = await oxyServices.getUserBySession(storedActiveSessionId);
|
|
226
228
|
loginSuccess(fullUser);
|
|
227
|
-
setMinimalUser({
|
|
228
|
-
id: fullUser.id,
|
|
229
|
-
username: fullUser.username,
|
|
230
|
-
avatar: fullUser.avatar
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
// Load sessions from backend
|
|
229
|
+
setMinimalUser({ id: fullUser.id, username: fullUser.username, avatar: fullUser.avatar });
|
|
234
230
|
const serverSessions = await oxyServices.getSessionsBySessionId(storedActiveSessionId);
|
|
235
|
-
const clientSessions: ClientSession[] = serverSessions.map(
|
|
236
|
-
sessionId:
|
|
237
|
-
deviceId:
|
|
238
|
-
expiresAt:
|
|
239
|
-
lastActive:
|
|
240
|
-
userId:
|
|
231
|
+
const clientSessions: ClientSession[] = serverSessions.map(s => ({
|
|
232
|
+
sessionId: s.sessionId,
|
|
233
|
+
deviceId: s.deviceId,
|
|
234
|
+
expiresAt: s.expiresAt || new Date().toISOString(),
|
|
235
|
+
lastActive: s.lastActive || new Date().toISOString(),
|
|
236
|
+
userId: s.userId || fullUser.id
|
|
241
237
|
}));
|
|
242
238
|
setSessions(clientSessions);
|
|
243
|
-
|
|
244
|
-
if (onAuthStateChange) {
|
|
245
|
-
onAuthStateChange(fullUser);
|
|
246
|
-
}
|
|
239
|
+
onAuthStateChange?.(fullUser);
|
|
247
240
|
} else {
|
|
248
|
-
console.log('Auth - session invalid, clearing storage');
|
|
249
241
|
await clearAllStorage();
|
|
250
242
|
}
|
|
251
|
-
} catch (
|
|
252
|
-
console.error('
|
|
243
|
+
} catch (e) {
|
|
244
|
+
console.error('Session validation error', e);
|
|
253
245
|
await clearAllStorage();
|
|
254
246
|
}
|
|
255
|
-
} else {
|
|
256
|
-
console.log('Auth - no stored session found, user needs to login');
|
|
257
247
|
}
|
|
258
|
-
|
|
259
|
-
|
|
248
|
+
setTokenReady(true);
|
|
249
|
+
} catch (e) {
|
|
250
|
+
console.error('Auth init error', e);
|
|
260
251
|
await clearAllStorage();
|
|
261
252
|
} finally {
|
|
262
253
|
useAuthStore.setState({ isLoading: false });
|
|
263
254
|
}
|
|
264
255
|
};
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
initAuth();
|
|
268
|
-
}
|
|
269
|
-
}, [storage, oxyServices, keys, onAuthStateChange, loginSuccess, setMinimalUser, clearAllStorage]);
|
|
256
|
+
initAuth();
|
|
257
|
+
}, [storage, oxyServices, keys, onAuthStateChange, loginSuccess, clearAllStorage]);
|
|
270
258
|
|
|
271
259
|
|
|
272
260
|
|
|
@@ -630,6 +618,26 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
630
618
|
}
|
|
631
619
|
}, [activeSessionId, oxyServices]);
|
|
632
620
|
|
|
621
|
+
// Language management method
|
|
622
|
+
const setLanguage = useCallback(async (languageId: string): Promise<void> => {
|
|
623
|
+
if (!storage) throw new Error('Storage not initialized');
|
|
624
|
+
|
|
625
|
+
try {
|
|
626
|
+
// Save language preference
|
|
627
|
+
await storage.setItem(keys.language, languageId);
|
|
628
|
+
setCurrentLanguage(languageId);
|
|
629
|
+
|
|
630
|
+
console.log(`Language changed to ${languageId}`);
|
|
631
|
+
|
|
632
|
+
// TODO: Here you can add any additional logic needed for app-wide language updates
|
|
633
|
+
// such as updating i18n configuration, refreshing translations, etc.
|
|
634
|
+
|
|
635
|
+
} catch (error) {
|
|
636
|
+
console.error('Error saving language preference:', error);
|
|
637
|
+
throw error;
|
|
638
|
+
}
|
|
639
|
+
}, [storage, keys.language]);
|
|
640
|
+
|
|
633
641
|
// Bottom sheet control methods
|
|
634
642
|
const showBottomSheet = useCallback((screenOrConfig?: string | { screen: string; props?: Record<string, any> }) => {
|
|
635
643
|
console.log('showBottomSheet called with:', screenOrConfig);
|
|
@@ -691,6 +699,24 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
691
699
|
});
|
|
692
700
|
|
|
693
701
|
// Context value - optimized to prevent unnecessary re-renders
|
|
702
|
+
// Lazy proxy to load the hook only when accessed, breaking the static import cycle.
|
|
703
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
|
|
704
|
+
const useFollowProxy: UseFollowHook = (userId?: string | string[]) => {
|
|
705
|
+
try {
|
|
706
|
+
// Dynamically require to avoid top-level cycle
|
|
707
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
708
|
+
const mod = require('../hooks/useFollow');
|
|
709
|
+
if (mod && typeof mod.useFollow === 'function') {
|
|
710
|
+
return mod.useFollow(userId);
|
|
711
|
+
}
|
|
712
|
+
console.warn('useFollow module did not export a function as expected');
|
|
713
|
+
return { isFollowing: false, isLoading: false, error: null, toggleFollow: async () => { }, setFollowStatus: () => { }, fetchStatus: async () => { }, clearError: () => { }, followerCount: null, followingCount: null, isLoadingCounts: false, fetchUserCounts: async () => { }, setFollowerCount: () => { }, setFollowingCount: () => { } } as any;
|
|
714
|
+
} catch (e) {
|
|
715
|
+
console.warn('Failed to dynamically load useFollow hook:', e);
|
|
716
|
+
return { isFollowing: false, isLoading: false, error: null, toggleFollow: async () => { }, setFollowStatus: () => { }, fetchStatus: async () => { }, clearError: () => { }, followerCount: null, followingCount: null, isLoadingCounts: false, fetchUserCounts: async () => { }, setFollowerCount: () => { }, setFollowingCount: () => { } } as any;
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
|
|
694
720
|
const contextValue: OxyContextState = useMemo(() => ({
|
|
695
721
|
user,
|
|
696
722
|
minimalUser,
|
|
@@ -699,6 +725,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
699
725
|
isAuthenticated,
|
|
700
726
|
isLoading,
|
|
701
727
|
error,
|
|
728
|
+
currentLanguage,
|
|
702
729
|
login,
|
|
703
730
|
logout,
|
|
704
731
|
logoutAll,
|
|
@@ -706,6 +733,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
706
733
|
switchSession,
|
|
707
734
|
removeSession,
|
|
708
735
|
refreshSessions,
|
|
736
|
+
setLanguage,
|
|
709
737
|
getDeviceSessions,
|
|
710
738
|
logoutAllDeviceSessions,
|
|
711
739
|
updateDeviceName,
|
|
@@ -713,7 +741,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
713
741
|
bottomSheetRef,
|
|
714
742
|
showBottomSheet,
|
|
715
743
|
hideBottomSheet,
|
|
716
|
-
useFollow:
|
|
744
|
+
useFollow: useFollowProxy,
|
|
717
745
|
}), [
|
|
718
746
|
user?.id, // Only depend on user ID, not the entire user object
|
|
719
747
|
minimalUser?.id,
|
|
@@ -722,6 +750,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
722
750
|
isAuthenticated,
|
|
723
751
|
isLoading,
|
|
724
752
|
error,
|
|
753
|
+
currentLanguage,
|
|
725
754
|
login,
|
|
726
755
|
logout,
|
|
727
756
|
logoutAll,
|
|
@@ -729,6 +758,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
729
758
|
switchSession,
|
|
730
759
|
removeSession,
|
|
731
760
|
refreshSessions,
|
|
761
|
+
setLanguage,
|
|
732
762
|
getDeviceSessions,
|
|
733
763
|
logoutAllDeviceSessions,
|
|
734
764
|
updateDeviceName,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Type-only definition for the useFollow hook to allow context exposure without runtime import cycles.
|
|
2
|
+
// Expand this as needed to better reflect the real return type.
|
|
3
|
+
|
|
4
|
+
export type SingleFollowResult = {
|
|
5
|
+
isFollowing: boolean;
|
|
6
|
+
isLoading: boolean;
|
|
7
|
+
error: string | null;
|
|
8
|
+
toggleFollow: () => Promise<void>;
|
|
9
|
+
setFollowStatus: (following: boolean) => void;
|
|
10
|
+
fetchStatus: () => Promise<void>;
|
|
11
|
+
clearError: () => void;
|
|
12
|
+
followerCount: number | null;
|
|
13
|
+
followingCount: number | null;
|
|
14
|
+
isLoadingCounts: boolean;
|
|
15
|
+
fetchUserCounts: () => Promise<void>;
|
|
16
|
+
setFollowerCount: (count: number) => void;
|
|
17
|
+
setFollowingCount: (count: number) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type MultiFollowResult = {
|
|
21
|
+
followData: Record<string, { isFollowing: boolean; isLoading: boolean; error: string | null }>;
|
|
22
|
+
toggleFollowForUser: (userId: string) => Promise<void>;
|
|
23
|
+
setFollowStatusForUser: (userId: string, following: boolean) => void;
|
|
24
|
+
fetchStatusForUser: (userId: string) => Promise<void>;
|
|
25
|
+
fetchAllStatuses: () => Promise<void>;
|
|
26
|
+
clearErrorForUser: (userId: string) => void;
|
|
27
|
+
isAnyLoading: boolean;
|
|
28
|
+
hasAnyError: boolean;
|
|
29
|
+
allFollowing: boolean;
|
|
30
|
+
allNotFollowing: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type UseFollowHook = (userId?: string | string[]) => SingleFollowResult | MultiFollowResult;
|
|
@@ -25,6 +25,8 @@ import UserLinksScreen from '../screens/UserLinksScreen';
|
|
|
25
25
|
import FileManagementScreen from '../screens/FileManagementScreen';
|
|
26
26
|
import RecoverAccountScreen from '../screens/RecoverAccountScreen';
|
|
27
27
|
import PaymentGatewayScreen from '../screens/PaymentGatewayScreen';
|
|
28
|
+
import WelcomeNewUserScreen from '../screens/WelcomeNewUserScreen';
|
|
29
|
+
import LanguageSelectorScreen from '../screens/LanguageSelectorScreen';
|
|
28
30
|
|
|
29
31
|
// Import types
|
|
30
32
|
import type { OxyRouterProps, RouteConfig } from './types';
|
|
@@ -115,6 +117,14 @@ const routes: Record<string, RouteConfig> = {
|
|
|
115
117
|
component: PaymentGatewayScreen,
|
|
116
118
|
snapPoints: ['60%', '90%'],
|
|
117
119
|
},
|
|
120
|
+
WelcomeNewUser: {
|
|
121
|
+
component: WelcomeNewUserScreen,
|
|
122
|
+
snapPoints: ['65%', '90%'],
|
|
123
|
+
},
|
|
124
|
+
LanguageSelector: {
|
|
125
|
+
component: LanguageSelectorScreen,
|
|
126
|
+
snapPoints: ['70%', '100%'],
|
|
127
|
+
},
|
|
118
128
|
};
|
|
119
129
|
|
|
120
130
|
const OxyRouter: React.FC<OxyRouterProps> = ({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OxyServices } from '../../core';
|
|
2
2
|
import type { User } from '../../models/interfaces';
|
|
3
3
|
import type { ComponentType, ReactNode } from 'react';
|
|
4
|
+
import type { QueryClient } from '@tanstack/react-query';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Base props for all screens in the Oxy UI system
|
|
@@ -138,4 +139,9 @@ export interface OxyProviderProps {
|
|
|
138
139
|
* @default true
|
|
139
140
|
*/
|
|
140
141
|
showInternalToaster?: boolean;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Optional QueryClient instance for React Query. If not provided, a sensible default is created.
|
|
145
|
+
*/
|
|
146
|
+
queryClient?: QueryClient;
|
|
141
147
|
}
|
|
@@ -16,13 +16,11 @@ import { toast } from '../../lib/sonner';
|
|
|
16
16
|
import { confirmAction } from '../utils/confirmAction';
|
|
17
17
|
import { Ionicons } from '@expo/vector-icons';
|
|
18
18
|
import { fontFamilies } from '../styles/fonts';
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
GroupedItem
|
|
25
|
-
} from '../components';
|
|
19
|
+
import ProfileCard from '../components/ProfileCard';
|
|
20
|
+
import Section from '../components/Section';
|
|
21
|
+
import QuickActions from '../components/QuickActions';
|
|
22
|
+
import GroupedSection from '../components/GroupedSection';
|
|
23
|
+
import GroupedItem from '../components/GroupedItem';
|
|
26
24
|
|
|
27
25
|
const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
28
26
|
onClose,
|
|
@@ -218,6 +216,14 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
218
216
|
subtitle: 'Manage notification settings',
|
|
219
217
|
onPress: () => toast.info('Notifications feature coming soon!'),
|
|
220
218
|
}] : []),
|
|
219
|
+
{
|
|
220
|
+
id: 'language',
|
|
221
|
+
icon: 'language',
|
|
222
|
+
iconColor: '#32D74B',
|
|
223
|
+
title: 'Language',
|
|
224
|
+
subtitle: 'Choose your preferred language',
|
|
225
|
+
onPress: () => navigate('LanguageSelector'),
|
|
226
|
+
},
|
|
221
227
|
{
|
|
222
228
|
id: 'help',
|
|
223
229
|
icon: 'help-circle',
|
|
@@ -192,7 +192,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
|
|
|
192
192
|
<>
|
|
193
193
|
<View style={styles.userIcon}>
|
|
194
194
|
<Avatar
|
|
195
|
-
uri={user?.avatar
|
|
195
|
+
uri={user?.avatar ? oxyServices.getFileDownloadUrl(user.avatar as string, 'thumb') : undefined}
|
|
196
196
|
name={user?.name?.full}
|
|
197
197
|
size={40}
|
|
198
198
|
theme={theme}
|
|
@@ -301,8 +301,8 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
|
|
|
301
301
|
customContent: (
|
|
302
302
|
<>
|
|
303
303
|
<View style={styles.userIcon}>
|
|
304
|
-
{account.avatar
|
|
305
|
-
<Image source={{ uri: account.avatar
|
|
304
|
+
{account.avatar ? (
|
|
305
|
+
<Image source={{ uri: oxyServices.getFileStreamUrl(account.avatar as string) }} style={styles.accountAvatarImage} />
|
|
306
306
|
) : (
|
|
307
307
|
<View style={styles.accountAvatarFallback}>
|
|
308
308
|
<Text style={styles.accountAvatarText}>
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import type { BaseScreenProps } from '../navigation/types';
|
|
16
16
|
import { useOxy } from '../context/OxyContext';
|
|
17
17
|
import Avatar from '../components/Avatar';
|
|
18
|
+
import type { FileMetadata } from '../../models/interfaces';
|
|
18
19
|
import OxyIcon from '../components/icon/OxyIcon';
|
|
19
20
|
import { Ionicons } from '@expo/vector-icons';
|
|
20
21
|
import { toast } from '../../lib/sonner';
|
|
@@ -29,7 +30,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
29
30
|
goBack,
|
|
30
31
|
navigate,
|
|
31
32
|
}) => {
|
|
32
|
-
const { user, oxyServices, isLoading: authLoading, isAuthenticated } = useOxy();
|
|
33
|
+
const { user, oxyServices, isLoading: authLoading, isAuthenticated, showBottomSheet } = useOxy();
|
|
33
34
|
const updateUser = useAuthStore((state) => state.updateUser);
|
|
34
35
|
const [isLoading, setIsLoading] = useState(false);
|
|
35
36
|
const [isSaving, setIsSaving] = useState(false);
|
|
@@ -45,7 +46,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
45
46
|
const [bio, setBio] = useState('');
|
|
46
47
|
const [location, setLocation] = useState('');
|
|
47
48
|
const [links, setLinks] = useState<string[]>([]);
|
|
48
|
-
const [
|
|
49
|
+
const [avatarFileId, setAvatarFileId] = useState('');
|
|
49
50
|
|
|
50
51
|
// Editing states
|
|
51
52
|
const [editingField, setEditingField] = useState<string | null>(null);
|
|
@@ -173,7 +174,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
173
174
|
setLinks([]);
|
|
174
175
|
setTempLinksWithMetadata([]);
|
|
175
176
|
}
|
|
176
|
-
|
|
177
|
+
setAvatarFileId(typeof user.avatar === 'string' ? user.avatar : '');
|
|
177
178
|
}
|
|
178
179
|
}, [user]);
|
|
179
180
|
|
|
@@ -203,8 +204,8 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
203
204
|
}
|
|
204
205
|
|
|
205
206
|
// Handle avatar
|
|
206
|
-
if (
|
|
207
|
-
updates.avatar =
|
|
207
|
+
if (avatarFileId !== (typeof user.avatar === 'string' ? user.avatar : '')) {
|
|
208
|
+
updates.avatar = avatarFileId;
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
await updateUser(updates, oxyServices);
|
|
@@ -225,14 +226,55 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
225
226
|
}
|
|
226
227
|
};
|
|
227
228
|
|
|
228
|
-
const
|
|
229
|
-
// Always use confirmAction for both web and native
|
|
229
|
+
const handleAvatarRemove = () => {
|
|
230
230
|
confirmAction('Remove your profile picture?', () => {
|
|
231
|
-
|
|
231
|
+
setAvatarFileId('');
|
|
232
232
|
toast.success('Avatar removed');
|
|
233
233
|
});
|
|
234
234
|
};
|
|
235
235
|
|
|
236
|
+
const openAvatarPicker = useCallback(() => {
|
|
237
|
+
showBottomSheet?.({
|
|
238
|
+
screen: 'FileManagement',
|
|
239
|
+
props: {
|
|
240
|
+
selectMode: true,
|
|
241
|
+
multiSelect: false,
|
|
242
|
+
afterSelect: 'back',
|
|
243
|
+
onSelect: (file: FileMetadata) => {
|
|
244
|
+
if (!file.contentType.startsWith('image/')) {
|
|
245
|
+
toast.error('Please select an image file');
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
// If already selected, do nothing
|
|
249
|
+
if (file.id === avatarFileId) {
|
|
250
|
+
toast.info?.('Avatar unchanged');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
setAvatarFileId(file.id);
|
|
254
|
+
toast.success('Avatar selected');
|
|
255
|
+
// Auto-save avatar immediately (does not close edit profile screen)
|
|
256
|
+
(async () => {
|
|
257
|
+
try {
|
|
258
|
+
console.log('[AccountSettings] Auto-saving avatar', file.id);
|
|
259
|
+
setIsSaving(true);
|
|
260
|
+
await updateUser({ avatar: file.id }, oxyServices);
|
|
261
|
+
// Force refresh current user cache (updateUser already does a fetch with force=true internally)
|
|
262
|
+
// Extra safeguard: ensure avatarFileId reflects saved id (already set) and trigger any dependent UI.
|
|
263
|
+
toast.success('Avatar updated');
|
|
264
|
+
} catch (e: any) {
|
|
265
|
+
console.error('[AccountSettings] Failed to auto-save avatar', e);
|
|
266
|
+
toast.error(e.message || 'Failed to update avatar');
|
|
267
|
+
} finally {
|
|
268
|
+
setIsSaving(false);
|
|
269
|
+
}
|
|
270
|
+
})();
|
|
271
|
+
},
|
|
272
|
+
// Limit to images client-side by using photos view if later exposed
|
|
273
|
+
disabledMimeTypes: ['video/', 'audio/', 'application/pdf']
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}, [showBottomSheet, oxyServices, avatarFileId, updateUser]);
|
|
277
|
+
|
|
236
278
|
const startEditing = (type: string, currentValue: string) => {
|
|
237
279
|
switch (type) {
|
|
238
280
|
case 'displayName':
|
|
@@ -871,19 +913,29 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
871
913
|
{/* Profile Picture Section */}
|
|
872
914
|
<View style={styles.section}>
|
|
873
915
|
<Text style={styles.sectionTitle}>Profile Picture</Text>
|
|
874
|
-
|
|
875
916
|
<GroupedSection
|
|
876
917
|
items={[
|
|
877
918
|
{
|
|
878
919
|
id: 'profile-photo',
|
|
879
|
-
icon:
|
|
920
|
+
icon: avatarFileId ? undefined : 'person',
|
|
880
921
|
iconColor: '#007AFF',
|
|
881
|
-
|
|
922
|
+
// Use download URL (includes token + fallback) instead of raw stream for reliability
|
|
923
|
+
image: avatarFileId ? oxyServices.getFileDownloadUrl(avatarFileId, 'thumb') : undefined,
|
|
882
924
|
imageSize: 40,
|
|
883
925
|
title: 'Profile Photo',
|
|
884
|
-
subtitle:
|
|
885
|
-
onPress:
|
|
926
|
+
subtitle: avatarFileId ? 'Tap to change your profile picture' : 'Tap to add a profile picture',
|
|
927
|
+
onPress: openAvatarPicker,
|
|
886
928
|
},
|
|
929
|
+
...(avatarFileId ? [
|
|
930
|
+
{
|
|
931
|
+
id: 'remove-profile-photo',
|
|
932
|
+
icon: 'trash',
|
|
933
|
+
iconColor: '#FF3B30',
|
|
934
|
+
title: 'Remove Photo',
|
|
935
|
+
subtitle: 'Delete current profile picture',
|
|
936
|
+
onPress: handleAvatarRemove,
|
|
937
|
+
}
|
|
938
|
+
] : []),
|
|
887
939
|
]}
|
|
888
940
|
theme={theme}
|
|
889
941
|
/>
|
|
@@ -294,8 +294,8 @@ const ModernAccountSwitcherScreen: React.FC<BaseScreenProps> = ({
|
|
|
294
294
|
|
|
295
295
|
<View style={[styles.settingItem, styles.firstSettingItem, styles.lastSettingItem, styles.currentAccountCard]}>
|
|
296
296
|
<View style={styles.userIcon}>
|
|
297
|
-
{user.avatar
|
|
298
|
-
<Image source={{ uri: user.avatar
|
|
297
|
+
{typeof user.avatar === 'string' && user.avatar ? (
|
|
298
|
+
<Image source={{ uri: oxyServices.getFileDownloadUrl(user.avatar as string, 'thumb') }} style={styles.accountAvatarImage} />
|
|
299
299
|
) : (
|
|
300
300
|
<View style={styles.accountAvatarFallback}>
|
|
301
301
|
<Text style={styles.accountAvatarText}>
|
|
@@ -356,8 +356,8 @@ const ModernAccountSwitcherScreen: React.FC<BaseScreenProps> = ({
|
|
|
356
356
|
<View style={styles.accountAvatarFallback}>
|
|
357
357
|
<ActivityIndicator size="small" color="#007AFF" />
|
|
358
358
|
</View>
|
|
359
|
-
) : userProfile?.avatar
|
|
360
|
-
<Image source={{ uri: userProfile.avatar
|
|
359
|
+
) : (typeof userProfile?.avatar === 'string' && userProfile.avatar) ? (
|
|
360
|
+
<Image source={{ uri: oxyServices.getFileDownloadUrl(userProfile.avatar as string, 'thumb') }} style={styles.accountAvatarImage} />
|
|
361
361
|
) : (
|
|
362
362
|
<View style={styles.accountAvatarFallback}>
|
|
363
363
|
<Text style={styles.accountAvatarText}>
|