@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.
Files changed (192) hide show
  1. package/lib/commonjs/ui/components/AnimationExample.js +213 -0
  2. package/lib/commonjs/ui/components/AnimationExample.js.map +1 -0
  3. package/lib/commonjs/ui/components/FollowButton.js +58 -47
  4. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  5. package/lib/commonjs/ui/components/GroupedItem.js +2 -1
  6. package/lib/commonjs/ui/components/GroupedItem.js.map +1 -1
  7. package/lib/commonjs/ui/components/GroupedSection.js +3 -0
  8. package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
  9. package/lib/commonjs/ui/components/Header.js +25 -11
  10. package/lib/commonjs/ui/components/Header.js.map +1 -1
  11. package/lib/commonjs/ui/components/OxyProvider.js +69 -33
  12. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  13. package/lib/commonjs/ui/components/ProfileCard.js +5 -1
  14. package/lib/commonjs/ui/components/ProfileCard.js.map +1 -1
  15. package/lib/commonjs/ui/components/index.js +0 -7
  16. package/lib/commonjs/ui/components/index.js.map +1 -1
  17. package/lib/commonjs/ui/components/internal/TextField.js +8 -4
  18. package/lib/commonjs/ui/components/internal/TextField.js.map +1 -1
  19. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js +161 -0
  20. package/lib/commonjs/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
  21. package/lib/commonjs/ui/context/OxyContext.js +97 -38
  22. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  23. package/lib/commonjs/ui/hooks/useFollow.types.js +2 -0
  24. package/lib/commonjs/ui/hooks/useFollow.types.js.map +1 -0
  25. package/lib/commonjs/ui/navigation/OxyRouter.js +10 -0
  26. package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
  27. package/lib/commonjs/ui/screens/AccountCenterScreen.js +26 -14
  28. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  29. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +3 -3
  30. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  31. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +64 -15
  32. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  33. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +4 -4
  34. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  35. package/lib/commonjs/ui/screens/FeedbackScreen.js +72 -75
  36. package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -1
  37. package/lib/commonjs/ui/screens/FileManagementScreen.js +286 -126
  38. package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
  39. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js +322 -0
  40. package/lib/commonjs/ui/screens/LanguageSelectorScreen.js.map +1 -0
  41. package/lib/commonjs/ui/screens/ProfileScreen.js +1 -1
  42. package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
  43. package/lib/commonjs/ui/screens/SessionManagementScreen.js +176 -174
  44. package/lib/commonjs/ui/screens/SessionManagementScreen.js.map +1 -1
  45. package/lib/commonjs/ui/screens/SignInScreen.js +43 -52
  46. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  47. package/lib/commonjs/ui/screens/SignUpScreen.js +6 -4
  48. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  49. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +386 -0
  50. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -0
  51. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js +25 -15
  52. package/lib/commonjs/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  53. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +16 -9
  54. package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  55. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +1 -1
  56. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  57. package/lib/commonjs/ui/styles/authStyles.js +1 -1
  58. package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
  59. package/lib/module/ui/components/AnimationExample.js +209 -0
  60. package/lib/module/ui/components/AnimationExample.js.map +1 -0
  61. package/lib/module/ui/components/FollowButton.js +58 -47
  62. package/lib/module/ui/components/FollowButton.js.map +1 -1
  63. package/lib/module/ui/components/GroupedItem.js +2 -1
  64. package/lib/module/ui/components/GroupedItem.js.map +1 -1
  65. package/lib/module/ui/components/GroupedSection.js +3 -0
  66. package/lib/module/ui/components/GroupedSection.js.map +1 -1
  67. package/lib/module/ui/components/Header.js +25 -11
  68. package/lib/module/ui/components/Header.js.map +1 -1
  69. package/lib/module/ui/components/OxyProvider.js +70 -34
  70. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  71. package/lib/module/ui/components/ProfileCard.js +5 -1
  72. package/lib/module/ui/components/ProfileCard.js.map +1 -1
  73. package/lib/module/ui/components/index.js +0 -1
  74. package/lib/module/ui/components/index.js.map +1 -1
  75. package/lib/module/ui/components/internal/TextField.js +8 -4
  76. package/lib/module/ui/components/internal/TextField.js.map +1 -1
  77. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js +156 -0
  78. package/lib/module/ui/components/photogrid/JustifiedPhotoGrid.js.map +1 -0
  79. package/lib/module/ui/context/OxyContext.js +97 -39
  80. package/lib/module/ui/context/OxyContext.js.map +1 -1
  81. package/lib/module/ui/hooks/useFollow.types.js +2 -0
  82. package/lib/module/ui/hooks/useFollow.types.js.map +1 -0
  83. package/lib/module/ui/navigation/OxyRouter.js +10 -0
  84. package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
  85. package/lib/module/ui/screens/AccountCenterScreen.js +12 -1
  86. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  87. package/lib/module/ui/screens/AccountOverviewScreen.js +3 -3
  88. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  89. package/lib/module/ui/screens/AccountSettingsScreen.js +64 -15
  90. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  91. package/lib/module/ui/screens/AccountSwitcherScreen.js +4 -4
  92. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  93. package/lib/module/ui/screens/FeedbackScreen.js +72 -75
  94. package/lib/module/ui/screens/FeedbackScreen.js.map +1 -1
  95. package/lib/module/ui/screens/FileManagementScreen.js +285 -125
  96. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  97. package/lib/module/ui/screens/LanguageSelectorScreen.js +319 -0
  98. package/lib/module/ui/screens/LanguageSelectorScreen.js.map +1 -0
  99. package/lib/module/ui/screens/ProfileScreen.js +1 -1
  100. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  101. package/lib/module/ui/screens/SessionManagementScreen.js +177 -175
  102. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  103. package/lib/module/ui/screens/SignInScreen.js +44 -53
  104. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  105. package/lib/module/ui/screens/SignUpScreen.js +6 -4
  106. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  107. package/lib/module/ui/screens/WelcomeNewUserScreen.js +382 -0
  108. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -0
  109. package/lib/module/ui/screens/internal/SignInPasswordStep.js +23 -14
  110. package/lib/module/ui/screens/internal/SignInPasswordStep.js.map +1 -1
  111. package/lib/module/ui/screens/internal/SignInUsernameStep.js +15 -9
  112. package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
  113. package/lib/module/ui/screens/karma/KarmaCenterScreen.js +1 -1
  114. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  115. package/lib/module/ui/styles/authStyles.js +1 -1
  116. package/lib/module/ui/styles/authStyles.js.map +1 -1
  117. package/lib/typescript/models/interfaces.d.ts +1 -5
  118. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  119. package/lib/typescript/models/session.d.ts +1 -4
  120. package/lib/typescript/models/session.d.ts.map +1 -1
  121. package/lib/typescript/ui/components/AnimationExample.d.ts +4 -0
  122. package/lib/typescript/ui/components/AnimationExample.d.ts.map +1 -0
  123. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  124. package/lib/typescript/ui/components/GroupedItem.d.ts.map +1 -1
  125. package/lib/typescript/ui/components/Header.d.ts +9 -0
  126. package/lib/typescript/ui/components/Header.d.ts.map +1 -1
  127. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  128. package/lib/typescript/ui/components/ProfileCard.d.ts +1 -3
  129. package/lib/typescript/ui/components/ProfileCard.d.ts.map +1 -1
  130. package/lib/typescript/ui/components/index.d.ts +0 -1
  131. package/lib/typescript/ui/components/index.d.ts.map +1 -1
  132. package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -1
  133. package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts +27 -0
  134. package/lib/typescript/ui/components/photogrid/JustifiedPhotoGrid.d.ts.map +1 -0
  135. package/lib/typescript/ui/context/OxyContext.d.ts +6 -2
  136. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  137. package/lib/typescript/ui/hooks/useFollow.types.d.ts +33 -0
  138. package/lib/typescript/ui/hooks/useFollow.types.d.ts.map +1 -0
  139. package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
  140. package/lib/typescript/ui/navigation/types.d.ts +5 -0
  141. package/lib/typescript/ui/navigation/types.d.ts.map +1 -1
  142. package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  143. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  144. package/lib/typescript/ui/screens/FeedbackScreen.d.ts.map +1 -1
  145. package/lib/typescript/ui/screens/FileManagementScreen.d.ts +18 -1
  146. package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
  147. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts +7 -0
  148. package/lib/typescript/ui/screens/LanguageSelectorScreen.d.ts.map +1 -0
  149. package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
  150. package/lib/typescript/ui/screens/SessionManagementScreen.d.ts.map +1 -1
  151. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  152. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  153. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts +13 -0
  154. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -0
  155. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts +5 -5
  156. package/lib/typescript/ui/screens/internal/SignInPasswordStep.d.ts.map +1 -1
  157. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts +4 -4
  158. package/lib/typescript/ui/screens/internal/SignInUsernameStep.d.ts.map +1 -1
  159. package/lib/typescript/ui/styles/authStyles.d.ts +1 -1
  160. package/package.json +10 -2
  161. package/src/models/interfaces.ts +2 -5
  162. package/src/models/session.ts +1 -4
  163. package/src/ui/components/AnimationExample.tsx +194 -0
  164. package/src/ui/components/FollowButton.tsx +65 -45
  165. package/src/ui/components/GroupedItem.tsx +1 -0
  166. package/src/ui/components/GroupedSection.tsx +1 -1
  167. package/src/ui/components/Header.tsx +36 -12
  168. package/src/ui/components/OxyProvider.tsx +66 -32
  169. package/src/ui/components/ProfileCard.tsx +6 -8
  170. package/src/ui/components/index.ts +0 -1
  171. package/src/ui/components/internal/TextField.tsx +12 -6
  172. package/src/ui/components/photogrid/JustifiedPhotoGrid.tsx +158 -0
  173. package/src/ui/context/OxyContext.tsx +84 -54
  174. package/src/ui/hooks/useFollow.types.ts +33 -0
  175. package/src/ui/navigation/OxyRouter.tsx +10 -0
  176. package/src/ui/navigation/types.ts +6 -0
  177. package/src/ui/screens/AccountCenterScreen.tsx +13 -7
  178. package/src/ui/screens/AccountOverviewScreen.tsx +3 -3
  179. package/src/ui/screens/AccountSettingsScreen.tsx +65 -13
  180. package/src/ui/screens/AccountSwitcherScreen.tsx +4 -4
  181. package/src/ui/screens/FeedbackScreen.tsx +57 -80
  182. package/src/ui/screens/FileManagementScreen.tsx +278 -175
  183. package/src/ui/screens/LanguageSelectorScreen.tsx +322 -0
  184. package/src/ui/screens/ProfileScreen.tsx +6 -1
  185. package/src/ui/screens/SessionManagementScreen.tsx +148 -151
  186. package/src/ui/screens/SignInScreen.tsx +43 -62
  187. package/src/ui/screens/SignUpScreen.tsx +3 -5
  188. package/src/ui/screens/WelcomeNewUserScreen.tsx +272 -0
  189. package/src/ui/screens/internal/SignInPasswordStep.tsx +28 -13
  190. package/src/ui/screens/internal/SignInUsernameStep.tsx +21 -11
  191. package/src/ui/screens/karma/KarmaCenterScreen.tsx +1 -1
  192. 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
- import { useFollow as baseUseFollow } from '../hooks/useFollow';
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, exposed for convenience so you can do const { useFollow } = useOxy();
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: any;
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('Failed to initialize storage:', 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
- // Effect to initialize authentication state - only store session ID
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
- // Only load the active session ID from storage
204
- const storedActiveSessionId = await storage.getItem(keys.activeSessionId);
205
-
206
- console.log('Auth - activeSessionId:', storedActiveSessionId);
207
- console.log('Auth - storage available:', !!storage);
208
- console.log('Auth - oxyServices available:', !!oxyServices);
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(serverSession => ({
236
- sessionId: serverSession.sessionId,
237
- deviceId: serverSession.deviceId,
238
- expiresAt: serverSession.expiresAt || new Date().toISOString(),
239
- lastActive: serverSession.lastActive || new Date().toISOString(),
240
- userId: serverSession.userId || fullUser.id
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 (error) {
252
- console.error('Auth - session validation error:', 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
- } catch (err) {
259
- console.error('Auth initialization error:', err);
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
- if (storage) {
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: baseUseFollow,
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
- ProfileCard,
21
- Section,
22
- QuickActions,
23
- GroupedSection,
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?.url}
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?.url ? (
305
- <Image source={{ uri: account.avatar.url }} style={styles.accountAvatarImage} />
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 [avatarUrl, setAvatarUrl] = useState('');
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
- setAvatarUrl(user.avatar?.url || '');
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 (avatarUrl !== user.avatar?.url) {
207
- updates.avatar = { url: avatarUrl };
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 handleAvatarUpdate = () => {
229
- // Always use confirmAction for both web and native
229
+ const handleAvatarRemove = () => {
230
230
  confirmAction('Remove your profile picture?', () => {
231
- setAvatarUrl('');
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: avatarUrl ? undefined : 'person',
920
+ icon: avatarFileId ? undefined : 'person',
880
921
  iconColor: '#007AFF',
881
- image: avatarUrl || undefined,
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: avatarUrl ? 'Tap to change your profile picture' : 'Tap to add a profile picture',
885
- onPress: handleAvatarUpdate,
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?.url ? (
298
- <Image source={{ uri: user.avatar.url }} style={styles.accountAvatarImage} />
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?.url ? (
360
- <Image source={{ uri: userProfile.avatar.url }} style={styles.accountAvatarImage} />
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}>