@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.
- package/README.md +14 -0
- package/lib/commonjs/assets/assets/illustrations/HighFive.tsx +41 -0
- package/lib/commonjs/assets/icons/OxyServices.js +1 -1
- package/lib/commonjs/assets/illustrations/HighFive.js +61 -0
- package/lib/commonjs/assets/illustrations/HighFive.js.map +1 -0
- package/lib/commonjs/core/index.js +24 -5
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/index.js +72 -23
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/node/createAuth.js +95 -0
- package/lib/commonjs/node/createAuth.js.map +1 -0
- package/lib/commonjs/node/index.js +15 -6
- package/lib/commonjs/node/index.js.map +1 -1
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/ui/components/Avatar.js +3 -3
- package/lib/commonjs/ui/components/Avatar.js.map +1 -1
- package/lib/commonjs/ui/components/FollowButton.js +82 -34
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/components/GroupedSection.js +1 -1
- package/lib/commonjs/ui/components/OxyLogo.js +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +146 -141
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/OxySignInButton.js +4 -4
- package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileCard.js +2 -2
- package/lib/commonjs/ui/components/Section.js +1 -1
- package/lib/commonjs/ui/components/SectionTitle.js +1 -1
- package/lib/commonjs/ui/components/icon/index.js +1 -1
- package/lib/commonjs/ui/components/index.js +12 -12
- package/lib/commonjs/ui/components/internal/GroupedPillButtons.js +213 -0
- package/lib/commonjs/ui/components/internal/GroupedPillButtons.js.map +1 -0
- package/lib/commonjs/ui/components/internal/TextField.js +576 -0
- package/lib/commonjs/ui/components/internal/TextField.js.map +1 -0
- package/lib/commonjs/ui/context/OxyContext.js +12 -2
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/index.js +13 -0
- package/lib/commonjs/ui/hooks/index.js.map +1 -0
- package/lib/commonjs/ui/hooks/useFollow.js +184 -0
- package/lib/commonjs/ui/hooks/useFollow.js.map +1 -0
- package/lib/commonjs/ui/index.js +44 -12
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/navigation/OxyRouter.js +23 -18
- package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountCenterScreen.js +21 -20
- package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountManagementDemo.js +3 -3
- package/lib/commonjs/ui/screens/AccountManagementDemo.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js +11 -10
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +8 -7
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +6 -5
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AppInfoScreen.js +12 -14
- package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/BillingManagementScreen.js +3 -3
- package/lib/commonjs/ui/screens/FeedbackScreen.js +1169 -0
- package/lib/commonjs/ui/screens/FeedbackScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/FileManagementScreen.js +3 -3
- package/lib/commonjs/ui/screens/PremiumSubscriptionScreen.js +3 -3
- package/lib/commonjs/ui/screens/ProfileScreen.js +2 -2
- package/lib/commonjs/ui/screens/SessionManagementScreen.js +2 -2
- package/lib/commonjs/ui/screens/SignInScreen.js +183 -305
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignUpScreen.js +811 -712
- package/lib/commonjs/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +8 -7
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js +2 -2
- package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js +1 -1
- package/lib/commonjs/ui/store/index.js +267 -0
- package/lib/commonjs/ui/store/index.js.map +1 -0
- package/lib/commonjs/ui/styles/index.js +2 -2
- package/lib/commonjs/ui/styles/theme.js +1 -1
- package/lib/commonjs/utils/index.js +1 -1
- package/lib/module/assets/assets/illustrations/HighFive.tsx +41 -0
- package/lib/module/assets/icons/OxyServices.js +1 -1
- package/lib/module/assets/icons/OxyServices.js.map +1 -1
- package/lib/module/assets/illustrations/HighFive.js +55 -0
- package/lib/module/assets/illustrations/HighFive.js.map +1 -0
- package/lib/module/core/index.js +24 -5
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/index.js +15 -11
- package/lib/module/index.js.map +1 -1
- package/lib/module/node/createAuth.js +90 -0
- package/lib/module/node/createAuth.js.map +1 -0
- package/lib/module/node/index.js +8 -4
- package/lib/module/node/index.js.map +1 -1
- package/lib/module/package.json +1 -0
- package/lib/module/ui/components/Avatar.js +2 -2
- package/lib/module/ui/components/Avatar.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +83 -35
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/GroupedSection.js +1 -1
- package/lib/module/ui/components/GroupedSection.js.map +1 -1
- package/lib/module/ui/components/OxyLogo.js +1 -1
- package/lib/module/ui/components/OxyLogo.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +143 -138
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/OxySignInButton.js +4 -4
- package/lib/module/ui/components/OxySignInButton.js.map +1 -1
- package/lib/module/ui/components/ProfileCard.js +2 -2
- package/lib/module/ui/components/ProfileCard.js.map +1 -1
- package/lib/module/ui/components/Section.js +1 -1
- package/lib/module/ui/components/Section.js.map +1 -1
- package/lib/module/ui/components/SectionTitle.js +1 -1
- package/lib/module/ui/components/SectionTitle.js.map +1 -1
- package/lib/module/ui/components/icon/index.js +1 -1
- package/lib/module/ui/components/icon/index.js.map +1 -1
- package/lib/module/ui/components/index.js +12 -12
- package/lib/module/ui/components/index.js.map +1 -1
- package/lib/module/ui/components/internal/GroupedPillButtons.js +208 -0
- package/lib/module/ui/components/internal/GroupedPillButtons.js.map +1 -0
- package/lib/module/ui/components/internal/TextField.js +571 -0
- package/lib/module/ui/components/internal/TextField.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +12 -2
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/index.js +4 -0
- package/lib/module/ui/hooks/index.js.map +1 -0
- package/lib/module/ui/hooks/useFollow.js +180 -0
- package/lib/module/ui/hooks/useFollow.js.map +1 -0
- package/lib/module/ui/index.js +21 -10
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/navigation/OxyRouter.js +23 -18
- package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/module/ui/screens/AccountCenterScreen.js +9 -8
- package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountManagementDemo.js +2 -2
- package/lib/module/ui/screens/AccountManagementDemo.js.map +1 -1
- package/lib/module/ui/screens/AccountOverviewScreen.js +11 -10
- package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +8 -7
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +6 -5
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/AppInfoScreen.js +12 -14
- package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/module/ui/screens/BillingManagementScreen.js +3 -3
- package/lib/module/ui/screens/BillingManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/FeedbackScreen.js +1164 -0
- package/lib/module/ui/screens/FeedbackScreen.js.map +1 -0
- package/lib/module/ui/screens/FileManagementScreen.js +3 -3
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/PremiumSubscriptionScreen.js +3 -3
- package/lib/module/ui/screens/PremiumSubscriptionScreen.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +2 -2
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/SessionManagementScreen.js +2 -2
- package/lib/module/ui/screens/SessionManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +183 -305
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/SignUpScreen.js +810 -712
- package/lib/module/ui/screens/SignUpScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js +8 -7
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js +2 -2
- package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaRulesScreen.js +1 -1
- package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
- package/lib/module/ui/store/index.js +255 -0
- package/lib/module/ui/store/index.js.map +1 -0
- package/lib/module/ui/styles/index.js +2 -2
- package/lib/module/ui/styles/index.js.map +1 -1
- package/lib/module/ui/styles/theme.js +1 -1
- package/lib/module/ui/styles/theme.js.map +1 -1
- package/lib/module/utils/index.js +1 -1
- package/lib/module/utils/index.js.map +1 -1
- package/lib/typescript/assets/illustrations/HighFive.d.ts +9 -0
- package/lib/typescript/assets/illustrations/HighFive.d.ts.map +1 -0
- package/lib/typescript/core/index.d.ts +16 -3
- package/lib/typescript/core/index.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +4 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/node/createAuth.d.ts +7 -0
- package/lib/typescript/node/createAuth.d.ts.map +1 -0
- package/lib/typescript/node/index.d.ts +2 -0
- package/lib/typescript/node/index.d.ts.map +1 -1
- package/lib/typescript/types/expo-vector-icons.d.ts +3 -0
- package/lib/typescript/types/express.d.ts +5 -0
- package/lib/typescript/types/react-redux.d.ts +5 -0
- package/lib/typescript/ui/components/FollowButton.d.ts +1 -0
- package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts +18 -0
- package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts.map +1 -0
- package/lib/typescript/ui/components/internal/TextField.d.ts +25 -0
- package/lib/typescript/ui/components/internal/TextField.d.ts.map +1 -0
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/index.d.ts +2 -0
- package/lib/typescript/ui/hooks/index.d.ts.map +1 -0
- package/lib/typescript/ui/hooks/useFollow.d.ts +43 -0
- package/lib/typescript/ui/hooks/useFollow.d.ts.map +1 -0
- package/lib/typescript/ui/index.d.ts +5 -0
- package/lib/typescript/ui/index.d.ts.map +1 -1
- package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountCenterScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSwitcherScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AppInfoScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/FeedbackScreen.d.ts +5 -0
- package/lib/typescript/ui/screens/FeedbackScreen.d.ts.map +1 -0
- 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/store/index.d.ts +66 -0
- package/lib/typescript/ui/store/index.d.ts.map +1 -0
- package/package.json +10 -25
- package/src/assets/illustrations/HighFive.tsx +41 -0
- package/src/core/index.ts +88 -3
- package/src/index.ts +19 -3
- package/src/node/createAuth.ts +116 -0
- package/src/node/index.ts +4 -0
- package/src/types/expo-vector-icons.d.ts +3 -0
- package/src/types/express.d.ts +5 -0
- package/src/types/react-redux.d.ts +5 -0
- package/src/ui/components/FollowButton.tsx +114 -56
- package/src/ui/components/OxyProvider.tsx +136 -135
- package/src/ui/components/OxySignInButton.tsx +2 -2
- package/src/ui/components/internal/GroupedPillButtons.tsx +253 -0
- package/src/ui/components/internal/TextField.tsx +694 -0
- package/src/ui/context/OxyContext.tsx +12 -2
- package/src/ui/hooks/index.ts +1 -0
- package/src/ui/hooks/useFollow.ts +173 -0
- package/src/ui/index.ts +15 -2
- package/src/ui/navigation/OxyRouter.tsx +8 -3
- package/src/ui/screens/AccountCenterScreen.tsx +17 -15
- package/src/ui/screens/AccountOverviewScreen.tsx +25 -25
- package/src/ui/screens/AccountSettingsScreen.tsx +30 -30
- package/src/ui/screens/AccountSwitcherScreen.tsx +34 -33
- package/src/ui/screens/AppInfoScreen.tsx +153 -155
- package/src/ui/screens/FeedbackScreen.tsx +1042 -0
- package/src/ui/screens/SignInScreen.tsx +181 -224
- package/src/ui/screens/SignUpScreen.tsx +772 -608
- package/src/ui/screens/karma/KarmaCenterScreen.tsx +4 -4
- 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
|
|
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 (!
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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 (!
|
|
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
|
|
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
|
|
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
|
>
|