@oxyhq/services 5.4.3 → 5.4.5

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 (233) hide show
  1. package/README.md +14 -0
  2. package/lib/commonjs/assets/assets/illustrations/HighFive.tsx +41 -0
  3. package/lib/commonjs/assets/icons/OxyServices.js +1 -1
  4. package/lib/commonjs/assets/illustrations/HighFive.js +61 -0
  5. package/lib/commonjs/assets/illustrations/HighFive.js.map +1 -0
  6. package/lib/commonjs/core/index.js +24 -5
  7. package/lib/commonjs/core/index.js.map +1 -1
  8. package/lib/commonjs/index.js +72 -23
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/node/createAuth.js +95 -0
  11. package/lib/commonjs/node/createAuth.js.map +1 -0
  12. package/lib/commonjs/node/index.js +15 -6
  13. package/lib/commonjs/node/index.js.map +1 -1
  14. package/lib/commonjs/package.json +1 -0
  15. package/lib/commonjs/ui/components/Avatar.js +3 -3
  16. package/lib/commonjs/ui/components/Avatar.js.map +1 -1
  17. package/lib/commonjs/ui/components/FollowButton.js +82 -34
  18. package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
  19. package/lib/commonjs/ui/components/GroupedSection.js +1 -1
  20. package/lib/commonjs/ui/components/OxyLogo.js +1 -1
  21. package/lib/commonjs/ui/components/OxyProvider.js +146 -141
  22. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  23. package/lib/commonjs/ui/components/OxySignInButton.js +4 -4
  24. package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
  25. package/lib/commonjs/ui/components/ProfileCard.js +2 -2
  26. package/lib/commonjs/ui/components/Section.js +1 -1
  27. package/lib/commonjs/ui/components/SectionTitle.js +1 -1
  28. package/lib/commonjs/ui/components/icon/index.js +1 -1
  29. package/lib/commonjs/ui/components/index.js +12 -12
  30. package/lib/commonjs/ui/components/internal/GroupedPillButtons.js +213 -0
  31. package/lib/commonjs/ui/components/internal/GroupedPillButtons.js.map +1 -0
  32. package/lib/commonjs/ui/components/internal/TextField.js +576 -0
  33. package/lib/commonjs/ui/components/internal/TextField.js.map +1 -0
  34. package/lib/commonjs/ui/context/OxyContext.js +12 -2
  35. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  36. package/lib/commonjs/ui/hooks/index.js +13 -0
  37. package/lib/commonjs/ui/hooks/index.js.map +1 -0
  38. package/lib/commonjs/ui/hooks/useFollow.js +184 -0
  39. package/lib/commonjs/ui/hooks/useFollow.js.map +1 -0
  40. package/lib/commonjs/ui/index.js +44 -12
  41. package/lib/commonjs/ui/index.js.map +1 -1
  42. package/lib/commonjs/ui/navigation/OxyRouter.js +23 -18
  43. package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
  44. package/lib/commonjs/ui/screens/AccountCenterScreen.js +21 -20
  45. package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
  46. package/lib/commonjs/ui/screens/AccountManagementDemo.js +3 -3
  47. package/lib/commonjs/ui/screens/AccountManagementDemo.js.map +1 -1
  48. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +11 -10
  49. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  50. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +8 -7
  51. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  52. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +6 -5
  53. package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
  54. package/lib/commonjs/ui/screens/AppInfoScreen.js +12 -14
  55. package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
  56. package/lib/commonjs/ui/screens/BillingManagementScreen.js +3 -3
  57. package/lib/commonjs/ui/screens/FeedbackScreen.js +1169 -0
  58. package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -0
  59. package/lib/commonjs/ui/screens/FileManagementScreen.js +3 -3
  60. package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js +3 -3
  61. package/lib/commonjs/ui/screens/ProfileScreen.js +2 -2
  62. package/lib/commonjs/ui/screens/SessionManagementScreen.js +2 -2
  63. package/lib/commonjs/ui/screens/SignInScreen.js +183 -305
  64. package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
  65. package/lib/commonjs/ui/screens/SignUpScreen.js +811 -712
  66. package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
  67. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +8 -7
  68. package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  69. package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js +2 -2
  70. package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js +1 -1
  71. package/lib/commonjs/ui/store/index.js +267 -0
  72. package/lib/commonjs/ui/store/index.js.map +1 -0
  73. package/lib/commonjs/ui/styles/index.js +2 -2
  74. package/lib/commonjs/ui/styles/theme.js +1 -1
  75. package/lib/commonjs/utils/index.js +1 -1
  76. package/lib/module/assets/assets/illustrations/HighFive.tsx +41 -0
  77. package/lib/module/assets/icons/OxyServices.js +1 -1
  78. package/lib/module/assets/icons/OxyServices.js.map +1 -1
  79. package/lib/module/assets/illustrations/HighFive.js +55 -0
  80. package/lib/module/assets/illustrations/HighFive.js.map +1 -0
  81. package/lib/module/core/index.js +24 -5
  82. package/lib/module/core/index.js.map +1 -1
  83. package/lib/module/index.js +15 -11
  84. package/lib/module/index.js.map +1 -1
  85. package/lib/module/node/createAuth.js +90 -0
  86. package/lib/module/node/createAuth.js.map +1 -0
  87. package/lib/module/node/index.js +8 -4
  88. package/lib/module/node/index.js.map +1 -1
  89. package/lib/module/package.json +1 -0
  90. package/lib/module/ui/components/Avatar.js +2 -2
  91. package/lib/module/ui/components/Avatar.js.map +1 -1
  92. package/lib/module/ui/components/FollowButton.js +83 -35
  93. package/lib/module/ui/components/FollowButton.js.map +1 -1
  94. package/lib/module/ui/components/GroupedSection.js +1 -1
  95. package/lib/module/ui/components/GroupedSection.js.map +1 -1
  96. package/lib/module/ui/components/OxyLogo.js +1 -1
  97. package/lib/module/ui/components/OxyLogo.js.map +1 -1
  98. package/lib/module/ui/components/OxyProvider.js +143 -138
  99. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  100. package/lib/module/ui/components/OxySignInButton.js +4 -4
  101. package/lib/module/ui/components/OxySignInButton.js.map +1 -1
  102. package/lib/module/ui/components/ProfileCard.js +2 -2
  103. package/lib/module/ui/components/ProfileCard.js.map +1 -1
  104. package/lib/module/ui/components/Section.js +1 -1
  105. package/lib/module/ui/components/Section.js.map +1 -1
  106. package/lib/module/ui/components/SectionTitle.js +1 -1
  107. package/lib/module/ui/components/SectionTitle.js.map +1 -1
  108. package/lib/module/ui/components/icon/index.js +1 -1
  109. package/lib/module/ui/components/icon/index.js.map +1 -1
  110. package/lib/module/ui/components/index.js +12 -12
  111. package/lib/module/ui/components/index.js.map +1 -1
  112. package/lib/module/ui/components/internal/GroupedPillButtons.js +208 -0
  113. package/lib/module/ui/components/internal/GroupedPillButtons.js.map +1 -0
  114. package/lib/module/ui/components/internal/TextField.js +571 -0
  115. package/lib/module/ui/components/internal/TextField.js.map +1 -0
  116. package/lib/module/ui/context/OxyContext.js +12 -2
  117. package/lib/module/ui/context/OxyContext.js.map +1 -1
  118. package/lib/module/ui/hooks/index.js +4 -0
  119. package/lib/module/ui/hooks/index.js.map +1 -0
  120. package/lib/module/ui/hooks/useFollow.js +180 -0
  121. package/lib/module/ui/hooks/useFollow.js.map +1 -0
  122. package/lib/module/ui/index.js +21 -10
  123. package/lib/module/ui/index.js.map +1 -1
  124. package/lib/module/ui/navigation/OxyRouter.js +23 -18
  125. package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
  126. package/lib/module/ui/screens/AccountCenterScreen.js +9 -8
  127. package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
  128. package/lib/module/ui/screens/AccountManagementDemo.js +2 -2
  129. package/lib/module/ui/screens/AccountManagementDemo.js.map +1 -1
  130. package/lib/module/ui/screens/AccountOverviewScreen.js +11 -10
  131. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  132. package/lib/module/ui/screens/AccountSettingsScreen.js +8 -7
  133. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  134. package/lib/module/ui/screens/AccountSwitcherScreen.js +6 -5
  135. package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
  136. package/lib/module/ui/screens/AppInfoScreen.js +12 -14
  137. package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
  138. package/lib/module/ui/screens/BillingManagementScreen.js +3 -3
  139. package/lib/module/ui/screens/BillingManagementScreen.js.map +1 -1
  140. package/lib/module/ui/screens/FeedbackScreen.js +1164 -0
  141. package/lib/module/ui/screens/FeedbackScreen.js.map +1 -0
  142. package/lib/module/ui/screens/FileManagementScreen.js +3 -3
  143. package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
  144. package/lib/module/ui/screens/PremiumSubscriptionScreen.js +3 -3
  145. package/lib/module/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
  146. package/lib/module/ui/screens/ProfileScreen.js +2 -2
  147. package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
  148. package/lib/module/ui/screens/SessionManagementScreen.js +2 -2
  149. package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
  150. package/lib/module/ui/screens/SignInScreen.js +183 -305
  151. package/lib/module/ui/screens/SignInScreen.js.map +1 -1
  152. package/lib/module/ui/screens/SignUpScreen.js +810 -712
  153. package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
  154. package/lib/module/ui/screens/karma/KarmaCenterScreen.js +8 -7
  155. package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
  156. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js +2 -2
  157. package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
  158. package/lib/module/ui/screens/karma/KarmaRulesScreen.js +1 -1
  159. package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
  160. package/lib/module/ui/store/index.js +255 -0
  161. package/lib/module/ui/store/index.js.map +1 -0
  162. package/lib/module/ui/styles/index.js +2 -2
  163. package/lib/module/ui/styles/index.js.map +1 -1
  164. package/lib/module/ui/styles/theme.js +1 -1
  165. package/lib/module/ui/styles/theme.js.map +1 -1
  166. package/lib/module/utils/index.js +1 -1
  167. package/lib/module/utils/index.js.map +1 -1
  168. package/lib/typescript/assets/illustrations/HighFive.d.ts +9 -0
  169. package/lib/typescript/assets/illustrations/HighFive.d.ts.map +1 -0
  170. package/lib/typescript/core/index.d.ts +16 -3
  171. package/lib/typescript/core/index.d.ts.map +1 -1
  172. package/lib/typescript/index.d.ts +4 -2
  173. package/lib/typescript/index.d.ts.map +1 -1
  174. package/lib/typescript/node/createAuth.d.ts +7 -0
  175. package/lib/typescript/node/createAuth.d.ts.map +1 -0
  176. package/lib/typescript/node/index.d.ts +2 -0
  177. package/lib/typescript/node/index.d.ts.map +1 -1
  178. package/lib/typescript/types/expo-vector-icons.d.ts +3 -0
  179. package/lib/typescript/types/express.d.ts +5 -0
  180. package/lib/typescript/types/react-redux.d.ts +5 -0
  181. package/lib/typescript/ui/components/FollowButton.d.ts +1 -0
  182. package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
  183. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  184. package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts +18 -0
  185. package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts.map +1 -0
  186. package/lib/typescript/ui/components/internal/TextField.d.ts +25 -0
  187. package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -0
  188. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  189. package/lib/typescript/ui/hooks/index.d.ts +2 -0
  190. package/lib/typescript/ui/hooks/index.d.ts.map +1 -0
  191. package/lib/typescript/ui/hooks/useFollow.d.ts +43 -0
  192. package/lib/typescript/ui/hooks/useFollow.d.ts.map +1 -0
  193. package/lib/typescript/ui/index.d.ts +5 -0
  194. package/lib/typescript/ui/index.d.ts.map +1 -1
  195. package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
  196. package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
  197. package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
  198. package/lib/typescript/ui/screens/AppInfoScreen.d.ts.map +1 -1
  199. package/lib/typescript/ui/screens/FeedbackScreen.d.ts +5 -0
  200. package/lib/typescript/ui/screens/FeedbackScreen.d.ts.map +1 -0
  201. package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
  202. package/lib/typescript/ui/screens/SignUpScreen.d.ts.map +1 -1
  203. package/lib/typescript/ui/store/index.d.ts +66 -0
  204. package/lib/typescript/ui/store/index.d.ts.map +1 -0
  205. package/package.json +10 -25
  206. package/src/assets/illustrations/HighFive.tsx +41 -0
  207. package/src/core/index.ts +88 -3
  208. package/src/index.ts +19 -3
  209. package/src/node/createAuth.ts +116 -0
  210. package/src/node/index.ts +4 -0
  211. package/src/types/expo-vector-icons.d.ts +3 -0
  212. package/src/types/express.d.ts +5 -0
  213. package/src/types/react-redux.d.ts +5 -0
  214. package/src/ui/components/FollowButton.tsx +114 -56
  215. package/src/ui/components/OxyProvider.tsx +136 -135
  216. package/src/ui/components/OxySignInButton.tsx +2 -2
  217. package/src/ui/components/internal/GroupedPillButtons.tsx +253 -0
  218. package/src/ui/components/internal/TextField.tsx +694 -0
  219. package/src/ui/context/OxyContext.tsx +12 -2
  220. package/src/ui/hooks/index.ts +1 -0
  221. package/src/ui/hooks/useFollow.ts +173 -0
  222. package/src/ui/index.ts +15 -2
  223. package/src/ui/navigation/OxyRouter.tsx +8 -3
  224. package/src/ui/screens/AccountCenterScreen.tsx +17 -15
  225. package/src/ui/screens/AccountOverviewScreen.tsx +25 -25
  226. package/src/ui/screens/AccountSettingsScreen.tsx +30 -30
  227. package/src/ui/screens/AccountSwitcherScreen.tsx +34 -33
  228. package/src/ui/screens/AppInfoScreen.tsx +153 -155
  229. package/src/ui/screens/FeedbackScreen.tsx +1042 -0
  230. package/src/ui/screens/SignInScreen.tsx +181 -224
  231. package/src/ui/screens/SignUpScreen.tsx +772 -608
  232. package/src/ui/screens/karma/KarmaCenterScreen.tsx +4 -4
  233. package/src/ui/store/index.ts +245 -0
@@ -11,7 +11,7 @@ export interface OxyContextState {
11
11
  minimalUser: MinimalUserData | null; // Minimal user data for UI
12
12
  sessions: SecureClientSession[]; // All active sessions
13
13
  activeSessionId: string | null;
14
- isAuthenticated: boolean;
14
+ isAuthenticated: boolean; // Single source of truth for authentication - use this instead of service methods
15
15
  isLoading: boolean;
16
16
  error: string | null;
17
17
 
@@ -640,13 +640,23 @@ export const OxyContextProvider: React.FC<OxyContextProviderProps> = ({
640
640
  }
641
641
  }, [bottomSheetRef]);
642
642
 
643
+ // Compute comprehensive authentication status
644
+ // This is the single source of truth for authentication across the entire app
645
+ const isAuthenticated = useMemo(() => {
646
+ // User is authenticated if:
647
+ // 1. We have a full user object loaded, OR
648
+ // 2. We have an active session with a valid token
649
+ // This covers both the loaded state and the loading-but-authenticated state
650
+ return !!user || (!!activeSessionId && !!oxyServices?.getCurrentUserId());
651
+ }, [user, activeSessionId, oxyServices]);
652
+
643
653
  // Context value
644
654
  const contextValue: OxyContextState = {
645
655
  user,
646
656
  minimalUser,
647
657
  sessions,
648
658
  activeSessionId,
649
- isAuthenticated: !!user,
659
+ isAuthenticated,
650
660
  isLoading,
651
661
  error,
652
662
  login,
@@ -0,0 +1 @@
1
+ export { useFollow } from './useFollow';
@@ -0,0 +1,173 @@
1
+ import { useDispatch, useSelector } from 'react-redux';
2
+ import { useCallback, useMemo } from 'react';
3
+ import { toggleFollowUser, setFollowingStatus, clearFollowError, fetchFollowStatus } from '../store';
4
+ import type { RootState } from '../store';
5
+ import { useOxy } from '../context/OxyContext';
6
+
7
+ // Memoized selector to prevent unnecessary re-renders
8
+ const createFollowSelector = (userId: string) => (state: RootState) => ({
9
+ isFollowing: state.follow.followingUsers[userId] ?? false,
10
+ isLoading: state.follow.loadingUsers[userId] ?? false,
11
+ error: state.follow.errors[userId] ?? null,
12
+ });
13
+
14
+ // Memoized selector for multiple users
15
+ const createMultipleFollowSelector = (userIds: string[]) => (state: RootState) => {
16
+ const followData: Record<string, { isFollowing: boolean; isLoading: boolean; error: string | null }> = {};
17
+ const followState = state.follow;
18
+
19
+ for (const userId of userIds) {
20
+ followData[userId] = {
21
+ isFollowing: followState.followingUsers[userId] ?? false,
22
+ isLoading: followState.loadingUsers[userId] ?? false,
23
+ error: followState.errors[userId] ?? null,
24
+ };
25
+ }
26
+
27
+ return {
28
+ followData,
29
+ isAnyLoading: userIds.some(uid => followState.loadingUsers[uid]),
30
+ hasAnyError: userIds.some(uid => followState.errors[uid]),
31
+ allFollowing: userIds.every(uid => followState.followingUsers[uid]),
32
+ allNotFollowing: userIds.every(uid => !followState.followingUsers[uid]),
33
+ };
34
+ };
35
+
36
+ /**
37
+ * Custom hook for managing follow/unfollow functionality
38
+ * Optimized to prevent unnecessary re-renders
39
+ * Can handle both single user and multiple users
40
+ */
41
+ export const useFollow = (userId?: string | string[]) => {
42
+ const dispatch = useDispatch();
43
+ const { oxyServices } = useOxy();
44
+
45
+ // Memoize user IDs to prevent recreation on every render
46
+ const userIds = useMemo(() => {
47
+ return Array.isArray(userId) ? userId : userId ? [userId] : [];
48
+ }, [userId]);
49
+
50
+ const isSingleUser = typeof userId === 'string';
51
+
52
+ // Memoize selectors to prevent recreation
53
+ const singleUserSelector = useMemo(() => {
54
+ return isSingleUser && userId ? createFollowSelector(userId) : null;
55
+ }, [isSingleUser, userId]);
56
+
57
+ const multipleUserSelector = useMemo(() => {
58
+ return !isSingleUser ? createMultipleFollowSelector(userIds) : null;
59
+ }, [isSingleUser, userIds]);
60
+
61
+ // Use appropriate selector based on mode
62
+ const singleUserData = useSelector(singleUserSelector || (() => ({ isFollowing: false, isLoading: false, error: null })));
63
+ const multipleUserData = useSelector(multipleUserSelector || (() => ({
64
+ followData: {},
65
+ isAnyLoading: false,
66
+ hasAnyError: false,
67
+ allFollowing: false,
68
+ allNotFollowing: true
69
+ })));
70
+
71
+ // Memoized callbacks to prevent recreation on every render
72
+ const toggleFollow = useCallback(async () => {
73
+ if (!isSingleUser || !userId) throw new Error('toggleFollow is only available for single user mode');
74
+
75
+ try {
76
+ const result = await dispatch(toggleFollowUser({
77
+ userId,
78
+ oxyServices,
79
+ isCurrentlyFollowing: singleUserData.isFollowing
80
+ })).unwrap();
81
+ return result;
82
+ } catch (error) {
83
+ throw error;
84
+ }
85
+ }, [dispatch, userId, oxyServices, singleUserData.isFollowing, isSingleUser]);
86
+
87
+ const setFollowStatus = useCallback((following: boolean) => {
88
+ if (!isSingleUser || !userId) throw new Error('setFollowStatus is only available for single user mode');
89
+ dispatch(setFollowingStatus({ userId, isFollowing: following }));
90
+ }, [dispatch, userId, isSingleUser]);
91
+
92
+ const fetchStatus = useCallback(async () => {
93
+ if (!isSingleUser || !userId) throw new Error('fetchStatus is only available for single user mode');
94
+
95
+ try {
96
+ await dispatch(fetchFollowStatus({ userId, oxyServices })).unwrap();
97
+ } catch (error) {
98
+ console.warn(`Failed to fetch follow status for user ${userId}:`, error);
99
+ }
100
+ }, [dispatch, userId, oxyServices, isSingleUser]);
101
+
102
+ const clearError = useCallback(() => {
103
+ if (!isSingleUser || !userId) throw new Error('clearError is only available for single user mode');
104
+ dispatch(clearFollowError(userId));
105
+ }, [dispatch, userId, isSingleUser]);
106
+
107
+ // Multiple user callbacks
108
+ const toggleFollowForUser = useCallback(async (targetUserId: string) => {
109
+ const currentState = multipleUserData.followData[targetUserId]?.isFollowing ?? false;
110
+ try {
111
+ const result = await dispatch(toggleFollowUser({
112
+ userId: targetUserId,
113
+ oxyServices,
114
+ isCurrentlyFollowing: currentState
115
+ })).unwrap();
116
+ return result;
117
+ } catch (error) {
118
+ throw error;
119
+ }
120
+ }, [dispatch, oxyServices, multipleUserData.followData]);
121
+
122
+ const setFollowStatusForUser = useCallback((targetUserId: string, following: boolean) => {
123
+ dispatch(setFollowingStatus({ userId: targetUserId, isFollowing: following }));
124
+ }, [dispatch]);
125
+
126
+ const fetchStatusForUser = useCallback(async (targetUserId: string) => {
127
+ try {
128
+ await dispatch(fetchFollowStatus({ userId: targetUserId, oxyServices })).unwrap();
129
+ } catch (error) {
130
+ console.warn(`Failed to fetch follow status for user ${targetUserId}:`, error);
131
+ }
132
+ }, [dispatch, oxyServices]);
133
+
134
+ const fetchAllStatuses = useCallback(async () => {
135
+ const promises = userIds.map(uid =>
136
+ dispatch(fetchFollowStatus({ userId: uid, oxyServices })).unwrap().catch((error: any) => {
137
+ console.warn(`Failed to fetch follow status for user ${uid}:`, error);
138
+ })
139
+ );
140
+ await Promise.all(promises);
141
+ }, [dispatch, userIds, oxyServices]);
142
+
143
+ const clearErrorForUser = useCallback((targetUserId: string) => {
144
+ dispatch(clearFollowError(targetUserId));
145
+ }, [dispatch]);
146
+
147
+ // Return appropriate interface based on mode
148
+ if (isSingleUser && userId) {
149
+ return {
150
+ isFollowing: singleUserData.isFollowing,
151
+ isLoading: singleUserData.isLoading,
152
+ error: singleUserData.error,
153
+ toggleFollow,
154
+ setFollowStatus,
155
+ fetchStatus,
156
+ clearError,
157
+ };
158
+ }
159
+
160
+ return {
161
+ followData: multipleUserData.followData,
162
+ toggleFollowForUser,
163
+ setFollowStatusForUser,
164
+ fetchStatusForUser,
165
+ fetchAllStatuses,
166
+ clearErrorForUser,
167
+ // Helper methods
168
+ isAnyLoading: multipleUserData.isAnyLoading,
169
+ hasAnyError: multipleUserData.hasAnyError,
170
+ allFollowing: multipleUserData.allFollowing,
171
+ allNotFollowing: multipleUserData.allNotFollowing,
172
+ };
173
+ };
package/src/ui/index.ts CHANGED
@@ -14,15 +14,28 @@ export { FontLoader, setupFonts } from './components/FontLoader';
14
14
  export { OxyIcon } from './components/icon';
15
15
  export type { IconProps } from './components/icon';
16
16
 
17
- export {
18
- OxyContextProvider,
17
+ export {
18
+ OxyContextProvider,
19
19
  useOxy,
20
20
  OxyContextState,
21
21
  OxyContextProviderProps
22
22
  } from './context/OxyContext';
23
23
 
24
+ // Redux store exports
25
+ export { store } from './store';
26
+ export type { RootState, AppDispatch } from './store';
27
+
24
28
  // Export styles
25
29
  export { fontFamilies, fontStyles } from './styles/fonts';
26
30
 
27
31
  // Export types for navigation (internal use)
28
32
  export * from './navigation/types';
33
+
34
+ // Hooks
35
+ export { useFollow } from './hooks';
36
+
37
+ // Screens
38
+ export { default as ProfileScreen } from './screens/ProfileScreen';
39
+
40
+ // Navigation
41
+ export { default as OxyRouter } from './navigation/OxyRouter';
@@ -13,6 +13,7 @@ import AccountSettingsScreen from '../screens/AccountSettingsScreen';
13
13
  import PremiumSubscriptionScreen from '../screens/PremiumSubscriptionScreen';
14
14
  import BillingManagementScreen from '../screens/BillingManagementScreen';
15
15
  import AppInfoScreen from '../screens/AppInfoScreen';
16
+ import FeedbackScreen from '../screens/FeedbackScreen';
16
17
  import KarmaCenterScreen from '../screens/karma/KarmaCenterScreen';
17
18
  import KarmaLeaderboardScreen from '../screens/karma/KarmaLeaderboardScreen';
18
19
  import KarmaRulesScreen from '../screens/karma/KarmaRulesScreen';
@@ -67,6 +68,10 @@ const routes: Record<string, RouteConfig> = {
67
68
  component: AppInfoScreen,
68
69
  snapPoints: ['60%', '90%'],
69
70
  },
71
+ Feedback: {
72
+ component: FeedbackScreen,
73
+ snapPoints: ['70%', '100%'],
74
+ },
70
75
  KarmaCenter: {
71
76
  component: KarmaCenterScreen,
72
77
  snapPoints: ['60%', '100%'],
@@ -138,7 +143,7 @@ const OxyRouter: React.FC<OxyRouterProps> = ({
138
143
  if (navigationRef) {
139
144
  navigationRef.current = navigate;
140
145
  }
141
-
146
+
142
147
  return () => {
143
148
  if (navigationRef) {
144
149
  navigationRef.current = null;
@@ -166,7 +171,7 @@ const OxyRouter: React.FC<OxyRouterProps> = ({
166
171
 
167
172
  // For React Native - check for global navigation events
168
173
  let intervalId: any = null;
169
-
174
+
170
175
  if (typeof document !== 'undefined') {
171
176
  // Web - use custom event listener
172
177
  document.addEventListener('oxy:navigate', handleNavigationEvent);
@@ -214,7 +219,7 @@ const OxyRouter: React.FC<OxyRouterProps> = ({
214
219
  // Render the current screen component
215
220
  const renderScreen = () => {
216
221
  const CurrentScreen = routes[currentScreen]?.component;
217
-
222
+
218
223
  console.log('[OxyRouter] Rendering screen:', currentScreen);
219
224
  console.log('[OxyRouter] Available routes:', Object.keys(routes));
220
225
  console.log('[OxyRouter] Current screen component found:', !!CurrentScreen);
@@ -15,12 +15,12 @@ import { packageInfo } from '../../constants/version';
15
15
  import { toast } from '../../lib/sonner';
16
16
  import { Ionicons } from '@expo/vector-icons';
17
17
  import { fontFamilies } from '../styles/fonts';
18
- import {
19
- ProfileCard,
20
- Section,
21
- QuickActions,
22
- GroupedSection,
23
- GroupedItem
18
+ import {
19
+ ProfileCard,
20
+ Section,
21
+ QuickActions,
22
+ GroupedSection,
23
+ GroupedItem
24
24
  } from '../components';
25
25
 
26
26
  const AccountCenterScreen: React.FC<BaseScreenProps> = ({
@@ -28,7 +28,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
28
28
  theme,
29
29
  navigate,
30
30
  }) => {
31
- const { user, logout, isLoading, sessions } = useOxy();
31
+ const { user, logout, isLoading, sessions, isAuthenticated } = useOxy();
32
32
 
33
33
  const isDarkTheme = theme === 'dark';
34
34
  const textColor = isDarkTheme ? '#FFFFFF' : '#000000';
@@ -69,7 +69,7 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
69
69
  );
70
70
  };
71
71
 
72
- if (!user) {
72
+ if (!isAuthenticated) {
73
73
  return (
74
74
  <View style={[styles.container, { backgroundColor }]}>
75
75
  <Text style={[styles.message, { color: textColor }]}>Not signed in</Text>
@@ -88,13 +88,15 @@ const AccountCenterScreen: React.FC<BaseScreenProps> = ({
88
88
  return (
89
89
  <View style={[styles.container, { backgroundColor }]}>
90
90
  {/* Header with user profile */}
91
- <ProfileCard
92
- user={user}
93
- theme={theme}
94
- onEditPress={() => navigate('AccountSettings', { activeTab: 'profile' })}
95
- onClosePress={onClose}
96
- showCloseButton={!!onClose}
97
- />
91
+ {user && (
92
+ <ProfileCard
93
+ user={user}
94
+ theme={theme}
95
+ onEditPress={() => navigate('AccountSettings', { activeTab: 'profile' })}
96
+ onClosePress={onClose}
97
+ showCloseButton={!!onClose}
98
+ />
99
+ )}
98
100
 
99
101
  <ScrollView style={styles.scrollView} contentContainerStyle={styles.scrollContainer} showsVerticalScrollIndicator={false}>
100
102
  {/* Quick Actions */}
@@ -34,7 +34,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
34
34
  theme,
35
35
  navigate,
36
36
  }) => {
37
- const { user, logout, isLoading, sessions, activeSessionId, oxyServices } = useOxy();
37
+ const { user, logout, isLoading, sessions, activeSessionId, oxyServices, isAuthenticated } = useOxy();
38
38
  const [showMoreAccounts, setShowMoreAccounts] = useState(false);
39
39
  const [additionalAccountsData, setAdditionalAccountsData] = useState<any[]>([]);
40
40
  const [loadingAdditionalAccounts, setLoadingAdditionalAccounts] = useState(false);
@@ -55,8 +55,8 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
55
55
  }, [theme]);
56
56
 
57
57
  // Memoize additional accounts filtering to prevent recalculation on every render
58
- const additionalAccounts = useMemo(() =>
59
- sessions.filter(session =>
58
+ const additionalAccounts = useMemo(() =>
59
+ sessions.filter(session =>
60
60
  session.sessionId !== activeSessionId && session.userId !== user?.id
61
61
  ), [sessions, activeSessionId, user?.id]
62
62
  );
@@ -170,7 +170,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
170
170
  );
171
171
  }, [handleLogout]);
172
172
 
173
- if (!user) {
173
+ if (!isAuthenticated) {
174
174
  return (
175
175
  <View style={[styles.container, { backgroundColor: themeStyles.backgroundColor }]}>
176
176
  <Text style={[styles.message, { color: themeStyles.textColor }]}>Not signed in</Text>
@@ -202,7 +202,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
202
202
  {/* User Profile Section */}
203
203
  <View style={styles.section}>
204
204
  <Text style={styles.sectionTitle}>Profile</Text>
205
-
205
+
206
206
  <View style={[styles.settingItem, styles.firstSettingItem, styles.lastSettingItem]}>
207
207
  <View style={styles.userIcon}>
208
208
  <Avatar
@@ -215,9 +215,9 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
215
215
  <View style={styles.settingInfo}>
216
216
  <View>
217
217
  <Text style={styles.settingLabel}>
218
- {typeof user.name === 'string' ? user.name : user.name?.full || user.name?.first || user.username}
218
+ {user ? (typeof user.name === 'string' ? user.name : user.name?.full || user.name?.first || user.username) : 'Loading...'}
219
219
  </Text>
220
- <Text style={styles.settingDescription}>{user.email || user.username}</Text>
220
+ <Text style={styles.settingDescription}>{user ? (user.email || user.username) : 'Loading...'}</Text>
221
221
  </View>
222
222
  </View>
223
223
  <TouchableOpacity
@@ -232,8 +232,8 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
232
232
  {/* Account Settings */}
233
233
  <View style={styles.section}>
234
234
  <Text style={styles.sectionTitle}>Account Settings</Text>
235
-
236
- <TouchableOpacity
235
+
236
+ <TouchableOpacity
237
237
  style={[styles.settingItem, styles.firstSettingItem]}
238
238
  onPress={() => navigate?.('AccountSettings', { activeTab: 'profile' })}
239
239
  >
@@ -247,7 +247,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
247
247
  <OxyIcon name="chevron-forward" size={16} color="#ccc" />
248
248
  </TouchableOpacity>
249
249
 
250
- <TouchableOpacity
250
+ <TouchableOpacity
251
251
  style={styles.settingItem}
252
252
  onPress={() => navigate?.('AccountSettings', { activeTab: 'password' })}
253
253
  >
@@ -261,7 +261,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
261
261
  <OxyIcon name="chevron-forward" size={16} color="#ccc" />
262
262
  </TouchableOpacity>
263
263
 
264
- <TouchableOpacity
264
+ <TouchableOpacity
265
265
  style={styles.settingItem}
266
266
  onPress={() => navigate?.('AccountSettings', { activeTab: 'notifications' })}
267
267
  >
@@ -275,7 +275,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
275
275
  <OxyIcon name="chevron-forward" size={16} color="#ccc" />
276
276
  </TouchableOpacity>
277
277
 
278
- <TouchableOpacity
278
+ <TouchableOpacity
279
279
  style={[styles.settingItem]}
280
280
  onPress={() => navigate?.('PremiumSubscription')}
281
281
  >
@@ -283,14 +283,14 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
283
283
  <OxyIcon name="star" size={20} color="#FFD700" style={styles.settingIcon} />
284
284
  <View>
285
285
  <Text style={styles.settingLabel}>Oxy+ Subscriptions</Text>
286
- <Text style={styles.settingDescription}>{user.isPremium ? 'Manage your premium plan' : 'Upgrade to premium features'}</Text>
286
+ <Text style={styles.settingDescription}>{user?.isPremium ? 'Manage your premium plan' : 'Upgrade to premium features'}</Text>
287
287
  </View>
288
288
  </View>
289
289
  <OxyIcon name="chevron-forward" size={16} color="#ccc" />
290
290
  </TouchableOpacity>
291
291
 
292
- {user.isPremium && (
293
- <TouchableOpacity
292
+ {user?.isPremium && (
293
+ <TouchableOpacity
294
294
  style={[styles.settingItem, styles.lastSettingItem]}
295
295
  onPress={() => navigate?.('BillingManagement')}
296
296
  >
@@ -310,7 +310,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
310
310
  {showMoreAccounts && (
311
311
  <View style={styles.section}>
312
312
  <Text style={styles.sectionTitle}>Additional Accounts{additionalAccountsData.length > 0 ? ` (${additionalAccountsData.length})` : ''}</Text>
313
-
313
+
314
314
  {loadingAdditionalAccounts ? (
315
315
  <View style={[styles.settingItem, styles.firstSettingItem, styles.lastSettingItem]}>
316
316
  <View style={styles.loadingContainer}>
@@ -324,7 +324,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
324
324
  <TouchableOpacity
325
325
  key={account.id}
326
326
  style={[
327
- styles.settingItem,
327
+ styles.settingItem,
328
328
  index === 0 && styles.firstSettingItem,
329
329
  index === additionalAccountsData.length - 1 && styles.lastSettingItem
330
330
  ]}
@@ -348,7 +348,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
348
348
  <View style={styles.settingInfo}>
349
349
  <View>
350
350
  <Text style={styles.settingLabel}>
351
- {typeof account.name === 'object'
351
+ {typeof account.name === 'object'
352
352
  ? account.name?.full || account.name?.first || account.username
353
353
  : account.name || account.username
354
354
  }
@@ -378,7 +378,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
378
378
  {showMoreAccounts && (
379
379
  <View style={styles.section}>
380
380
  <Text style={styles.sectionTitle}>Account Management</Text>
381
-
381
+
382
382
  <TouchableOpacity
383
383
  style={[styles.settingItem, styles.firstSettingItem]}
384
384
  onPress={handleAddAccount}
@@ -412,7 +412,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
412
412
  {/* Quick Actions */}
413
413
  <View style={styles.section}>
414
414
  <Text style={styles.sectionTitle}>Quick Actions</Text>
415
-
415
+
416
416
  <TouchableOpacity
417
417
  style={[styles.settingItem, styles.firstSettingItem]}
418
418
  onPress={() => setShowMoreAccounts(!showMoreAccounts)}
@@ -424,9 +424,9 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
424
424
  {showMoreAccounts ? 'Hide' : 'Show'} Account Switcher
425
425
  </Text>
426
426
  <Text style={styles.settingDescription}>
427
- {showMoreAccounts
428
- ? 'Hide account switcher'
429
- : additionalAccountsData.length > 0
427
+ {showMoreAccounts
428
+ ? 'Hide account switcher'
429
+ : additionalAccountsData.length > 0
430
430
  ? `Switch between ${additionalAccountsData.length + 1} accounts`
431
431
  : loadingAdditionalAccounts
432
432
  ? 'Loading additional accounts...'
@@ -470,7 +470,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
470
470
  {/* Support & Settings */}
471
471
  <View style={styles.section}>
472
472
  <Text style={styles.sectionTitle}>Support & Settings</Text>
473
-
473
+
474
474
  <TouchableOpacity
475
475
  style={[styles.settingItem, styles.firstSettingItem]}
476
476
  onPress={() => toast.info('Account preferences coming soon!')}
@@ -544,7 +544,7 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
544
544
 
545
545
  {/* Sign Out */}
546
546
  <View style={styles.section}>
547
- <TouchableOpacity
547
+ <TouchableOpacity
548
548
  style={[styles.settingItem, styles.firstSettingItem, styles.lastSettingItem, styles.signOutButton]}
549
549
  onPress={confirmLogout}
550
550
  >