@oxyhq/services 5.4.4 → 5.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/core/index.js +81 -3
- package/lib/commonjs/core/index.js.map +1 -1
- package/lib/commonjs/index.js +50 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/FollowButton.js +94 -31
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/components/OxySignInButton.js +2 -2
- package/lib/commonjs/ui/components/OxySignInButton.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +11 -1
- 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 +203 -0
- package/lib/commonjs/ui/hooks/useFollow.js.map +1 -0
- package/lib/commonjs/ui/index.js +25 -1
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountCenterScreen.js +4 -3
- package/lib/commonjs/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js +7 -6
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +3 -2
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js +3 -2
- package/lib/commonjs/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AppInfoScreen.js +25 -45
- package/lib/commonjs/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +9 -27
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +5 -4
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/store/index.js +223 -4
- package/lib/commonjs/ui/store/index.js.map +1 -1
- package/lib/module/core/index.js +81 -3
- package/lib/module/core/index.js.map +1 -1
- package/lib/module/index.js +6 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +95 -32
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/OxySignInButton.js +2 -2
- package/lib/module/ui/components/OxySignInButton.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +11 -1
- 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 +199 -0
- package/lib/module/ui/hooks/useFollow.js.map +1 -0
- package/lib/module/ui/index.js +9 -0
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/screens/AccountCenterScreen.js +4 -3
- package/lib/module/ui/screens/AccountCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountOverviewScreen.js +7 -6
- package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +3 -2
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSwitcherScreen.js +3 -2
- package/lib/module/ui/screens/AccountSwitcherScreen.js.map +1 -1
- package/lib/module/ui/screens/AppInfoScreen.js +25 -45
- package/lib/module/ui/screens/AppInfoScreen.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +9 -27
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +1 -1
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js +5 -4
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/module/ui/store/index.js +219 -4
- package/lib/module/ui/store/index.js.map +1 -1
- package/lib/typescript/core/index.d.ts +44 -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/ui/components/FollowButton.d.ts +1 -0
- package/lib/typescript/ui/components/FollowButton.d.ts.map +1 -1
- 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 +3 -0
- package/lib/typescript/ui/index.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/FileManagementScreen.d.ts.map +1 -1
- package/lib/typescript/ui/store/index.d.ts +47 -0
- package/lib/typescript/ui/store/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/index.ts +88 -3
- package/src/index.ts +19 -3
- package/src/ui/components/FollowButton.tsx +128 -56
- package/src/ui/components/OxySignInButton.tsx +2 -2
- package/src/ui/context/OxyContext.tsx +12 -2
- package/src/ui/hooks/index.ts +1 -0
- package/src/ui/hooks/useFollow.ts +193 -0
- package/src/ui/index.ts +9 -0
- 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 +173 -192
- package/src/ui/screens/FileManagementScreen.tsx +248 -268
- package/src/ui/screens/SignInScreen.tsx +2 -2
- package/src/ui/screens/karma/KarmaCenterScreen.tsx +4 -4
- package/src/ui/store/index.ts +202 -3
|
@@ -0,0 +1,193 @@
|
|
|
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
|
+
// Defensive check for follow state
|
|
20
|
+
if (!followState) {
|
|
21
|
+
// Return default values if follow state is not initialized
|
|
22
|
+
for (const userId of userIds) {
|
|
23
|
+
followData[userId] = {
|
|
24
|
+
isFollowing: false,
|
|
25
|
+
isLoading: false,
|
|
26
|
+
error: null,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
followData,
|
|
32
|
+
isAnyLoading: false,
|
|
33
|
+
hasAnyError: false,
|
|
34
|
+
allFollowing: false,
|
|
35
|
+
allNotFollowing: true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const userId of userIds) {
|
|
40
|
+
followData[userId] = {
|
|
41
|
+
isFollowing: followState.followingUsers?.[userId] ?? false,
|
|
42
|
+
isLoading: followState.loadingUsers?.[userId] ?? false,
|
|
43
|
+
error: followState.errors?.[userId] ?? null,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
followData,
|
|
49
|
+
isAnyLoading: userIds.some(uid => followState.loadingUsers?.[uid]),
|
|
50
|
+
hasAnyError: userIds.some(uid => followState.errors?.[uid]),
|
|
51
|
+
allFollowing: userIds.every(uid => followState.followingUsers?.[uid]),
|
|
52
|
+
allNotFollowing: userIds.every(uid => !followState.followingUsers?.[uid]),
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Custom hook for managing follow/unfollow functionality
|
|
58
|
+
* Optimized to prevent unnecessary re-renders
|
|
59
|
+
* Can handle both single user and multiple users
|
|
60
|
+
*/
|
|
61
|
+
export const useFollow = (userId?: string | string[]) => {
|
|
62
|
+
const dispatch = useDispatch();
|
|
63
|
+
const { oxyServices } = useOxy();
|
|
64
|
+
|
|
65
|
+
// Memoize user IDs to prevent recreation on every render
|
|
66
|
+
const userIds = useMemo(() => {
|
|
67
|
+
return Array.isArray(userId) ? userId : userId ? [userId] : [];
|
|
68
|
+
}, [userId]);
|
|
69
|
+
|
|
70
|
+
const isSingleUser = typeof userId === 'string';
|
|
71
|
+
|
|
72
|
+
// Memoize selectors to prevent recreation
|
|
73
|
+
const singleUserSelector = useMemo(() => {
|
|
74
|
+
return isSingleUser && userId ? createFollowSelector(userId) : null;
|
|
75
|
+
}, [isSingleUser, userId]);
|
|
76
|
+
|
|
77
|
+
const multipleUserSelector = useMemo(() => {
|
|
78
|
+
return !isSingleUser ? createMultipleFollowSelector(userIds) : null;
|
|
79
|
+
}, [isSingleUser, userIds]);
|
|
80
|
+
|
|
81
|
+
// Use appropriate selector based on mode
|
|
82
|
+
const singleUserData = useSelector(singleUserSelector || (() => ({ isFollowing: false, isLoading: false, error: null })));
|
|
83
|
+
const multipleUserData = useSelector(multipleUserSelector || (() => ({
|
|
84
|
+
followData: {},
|
|
85
|
+
isAnyLoading: false,
|
|
86
|
+
hasAnyError: false,
|
|
87
|
+
allFollowing: false,
|
|
88
|
+
allNotFollowing: true
|
|
89
|
+
})));
|
|
90
|
+
|
|
91
|
+
// Memoized callbacks to prevent recreation on every render
|
|
92
|
+
const toggleFollow = useCallback(async () => {
|
|
93
|
+
if (!isSingleUser || !userId) throw new Error('toggleFollow is only available for single user mode');
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const result = await dispatch(toggleFollowUser({
|
|
97
|
+
userId,
|
|
98
|
+
oxyServices,
|
|
99
|
+
isCurrentlyFollowing: singleUserData.isFollowing
|
|
100
|
+
})).unwrap();
|
|
101
|
+
return result;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}, [dispatch, userId, oxyServices, singleUserData.isFollowing, isSingleUser]);
|
|
106
|
+
|
|
107
|
+
const setFollowStatus = useCallback((following: boolean) => {
|
|
108
|
+
if (!isSingleUser || !userId) throw new Error('setFollowStatus is only available for single user mode');
|
|
109
|
+
dispatch(setFollowingStatus({ userId, isFollowing: following }));
|
|
110
|
+
}, [dispatch, userId, isSingleUser]);
|
|
111
|
+
|
|
112
|
+
const fetchStatus = useCallback(async () => {
|
|
113
|
+
if (!isSingleUser || !userId) throw new Error('fetchStatus is only available for single user mode');
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await dispatch(fetchFollowStatus({ userId, oxyServices })).unwrap();
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.warn(`Failed to fetch follow status for user ${userId}:`, error);
|
|
119
|
+
}
|
|
120
|
+
}, [dispatch, userId, oxyServices, isSingleUser]);
|
|
121
|
+
|
|
122
|
+
const clearError = useCallback(() => {
|
|
123
|
+
if (!isSingleUser || !userId) throw new Error('clearError is only available for single user mode');
|
|
124
|
+
dispatch(clearFollowError(userId));
|
|
125
|
+
}, [dispatch, userId, isSingleUser]);
|
|
126
|
+
|
|
127
|
+
// Multiple user callbacks
|
|
128
|
+
const toggleFollowForUser = useCallback(async (targetUserId: string) => {
|
|
129
|
+
const currentState = multipleUserData.followData[targetUserId]?.isFollowing ?? false;
|
|
130
|
+
try {
|
|
131
|
+
const result = await dispatch(toggleFollowUser({
|
|
132
|
+
userId: targetUserId,
|
|
133
|
+
oxyServices,
|
|
134
|
+
isCurrentlyFollowing: currentState
|
|
135
|
+
})).unwrap();
|
|
136
|
+
return result;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}, [dispatch, oxyServices, multipleUserData.followData]);
|
|
141
|
+
|
|
142
|
+
const setFollowStatusForUser = useCallback((targetUserId: string, following: boolean) => {
|
|
143
|
+
dispatch(setFollowingStatus({ userId: targetUserId, isFollowing: following }));
|
|
144
|
+
}, [dispatch]);
|
|
145
|
+
|
|
146
|
+
const fetchStatusForUser = useCallback(async (targetUserId: string) => {
|
|
147
|
+
try {
|
|
148
|
+
await dispatch(fetchFollowStatus({ userId: targetUserId, oxyServices })).unwrap();
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.warn(`Failed to fetch follow status for user ${targetUserId}:`, error);
|
|
151
|
+
}
|
|
152
|
+
}, [dispatch, oxyServices]);
|
|
153
|
+
|
|
154
|
+
const fetchAllStatuses = useCallback(async () => {
|
|
155
|
+
const promises = userIds.map(uid =>
|
|
156
|
+
dispatch(fetchFollowStatus({ userId: uid, oxyServices })).unwrap().catch((error: any) => {
|
|
157
|
+
console.warn(`Failed to fetch follow status for user ${uid}:`, error);
|
|
158
|
+
})
|
|
159
|
+
);
|
|
160
|
+
await Promise.all(promises);
|
|
161
|
+
}, [dispatch, userIds, oxyServices]);
|
|
162
|
+
|
|
163
|
+
const clearErrorForUser = useCallback((targetUserId: string) => {
|
|
164
|
+
dispatch(clearFollowError(targetUserId));
|
|
165
|
+
}, [dispatch]);
|
|
166
|
+
|
|
167
|
+
// Return appropriate interface based on mode
|
|
168
|
+
if (isSingleUser && userId) {
|
|
169
|
+
return {
|
|
170
|
+
isFollowing: singleUserData.isFollowing,
|
|
171
|
+
isLoading: singleUserData.isLoading,
|
|
172
|
+
error: singleUserData.error,
|
|
173
|
+
toggleFollow,
|
|
174
|
+
setFollowStatus,
|
|
175
|
+
fetchStatus,
|
|
176
|
+
clearError,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
followData: multipleUserData.followData,
|
|
182
|
+
toggleFollowForUser,
|
|
183
|
+
setFollowStatusForUser,
|
|
184
|
+
fetchStatusForUser,
|
|
185
|
+
fetchAllStatuses,
|
|
186
|
+
clearErrorForUser,
|
|
187
|
+
// Helper methods
|
|
188
|
+
isAnyLoading: multipleUserData.isAnyLoading,
|
|
189
|
+
hasAnyError: multipleUserData.hasAnyError,
|
|
190
|
+
allFollowing: multipleUserData.allFollowing,
|
|
191
|
+
allNotFollowing: multipleUserData.allNotFollowing,
|
|
192
|
+
};
|
|
193
|
+
};
|
package/src/ui/index.ts
CHANGED
|
@@ -30,3 +30,12 @@ export { fontFamilies, fontStyles } from './styles/fonts';
|
|
|
30
30
|
|
|
31
31
|
// Export types for navigation (internal use)
|
|
32
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';
|
|
@@ -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
|
>
|
|
@@ -24,7 +24,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
24
24
|
goBack,
|
|
25
25
|
navigate,
|
|
26
26
|
}) => {
|
|
27
|
-
const { user, oxyServices, isLoading: authLoading } = useOxy();
|
|
27
|
+
const { user, oxyServices, isLoading: authLoading, isAuthenticated } = useOxy();
|
|
28
28
|
const [isLoading, setIsLoading] = useState(false);
|
|
29
29
|
const [isSaving, setIsSaving] = useState(false);
|
|
30
30
|
|
|
@@ -74,10 +74,10 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
74
74
|
// Load user data
|
|
75
75
|
useEffect(() => {
|
|
76
76
|
if (user) {
|
|
77
|
-
const userDisplayName = typeof user.name === 'string'
|
|
78
|
-
? user.name
|
|
77
|
+
const userDisplayName = typeof user.name === 'string'
|
|
78
|
+
? user.name
|
|
79
79
|
: user.name?.full || user.name?.first || '';
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
setDisplayName(userDisplayName);
|
|
82
82
|
setUsername(user.username || '');
|
|
83
83
|
setEmail(user.email || '');
|
|
@@ -115,9 +115,9 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
115
115
|
|
|
116
116
|
await oxyServices.updateProfile(updates);
|
|
117
117
|
toast.success('Profile updated successfully');
|
|
118
|
-
|
|
118
|
+
|
|
119
119
|
animateSaveButton(1); // Scale back to normal
|
|
120
|
-
|
|
120
|
+
|
|
121
121
|
if (onClose) {
|
|
122
122
|
onClose();
|
|
123
123
|
} else if (goBack) {
|
|
@@ -182,7 +182,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
182
182
|
|
|
183
183
|
const saveField = (type: string) => {
|
|
184
184
|
animateSaveButton(0.95); // Scale down slightly for animation
|
|
185
|
-
|
|
185
|
+
|
|
186
186
|
switch (type) {
|
|
187
187
|
case 'displayName':
|
|
188
188
|
setDisplayName(tempDisplayName);
|
|
@@ -203,7 +203,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
203
203
|
setWebsite(tempWebsite);
|
|
204
204
|
break;
|
|
205
205
|
}
|
|
206
|
-
|
|
206
|
+
|
|
207
207
|
// Brief delay for animation, then reset and close editing
|
|
208
208
|
setTimeout(() => {
|
|
209
209
|
animateSaveButton(1);
|
|
@@ -263,7 +263,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
263
263
|
default: return '';
|
|
264
264
|
}
|
|
265
265
|
})();
|
|
266
|
-
|
|
266
|
+
|
|
267
267
|
const setTempValue = (text: string) => {
|
|
268
268
|
switch (type) {
|
|
269
269
|
case 'displayName': setTempDisplayName(text); break;
|
|
@@ -285,7 +285,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
285
285
|
<TextInput
|
|
286
286
|
style={[
|
|
287
287
|
config.multiline ? styles.editingFieldTextArea : styles.editingFieldInput,
|
|
288
|
-
{
|
|
288
|
+
{
|
|
289
289
|
backgroundColor: themeStyles.isDarkTheme ? '#333' : '#fff',
|
|
290
290
|
color: themeStyles.isDarkTheme ? '#fff' : '#000',
|
|
291
291
|
borderColor: themeStyles.primaryColor
|
|
@@ -326,7 +326,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
326
326
|
];
|
|
327
327
|
|
|
328
328
|
return (
|
|
329
|
-
<TouchableOpacity
|
|
329
|
+
<TouchableOpacity
|
|
330
330
|
style={itemStyles}
|
|
331
331
|
onPress={() => startEditing(type, value)}
|
|
332
332
|
>
|
|
@@ -344,7 +344,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
344
344
|
);
|
|
345
345
|
};
|
|
346
346
|
|
|
347
|
-
if (authLoading || !
|
|
347
|
+
if (authLoading || !isAuthenticated) {
|
|
348
348
|
return (
|
|
349
349
|
<View style={[styles.container, { backgroundColor: themeStyles.backgroundColor, justifyContent: 'center' }]}>
|
|
350
350
|
<ActivityIndicator size="large" color={themeStyles.primaryColor} />
|
|
@@ -363,14 +363,14 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
363
363
|
<Ionicons name="close" size={24} color="#666" />
|
|
364
364
|
</TouchableOpacity>
|
|
365
365
|
<Animated.View style={{ transform: [{ scale: saveButtonScale }] }}>
|
|
366
|
-
<TouchableOpacity
|
|
366
|
+
<TouchableOpacity
|
|
367
367
|
style={[
|
|
368
|
-
styles.saveHeaderButton,
|
|
369
|
-
{
|
|
368
|
+
styles.saveHeaderButton,
|
|
369
|
+
{
|
|
370
370
|
opacity: isSaving ? 0.7 : 1,
|
|
371
371
|
backgroundColor: editingField ? getFieldIcon(editingField).color : '#007AFF'
|
|
372
372
|
}
|
|
373
|
-
]}
|
|
373
|
+
]}
|
|
374
374
|
onPress={() => saveField(editingField)}
|
|
375
375
|
disabled={isSaving}
|
|
376
376
|
>
|
|
@@ -384,11 +384,11 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
384
384
|
</View>
|
|
385
385
|
<View style={styles.editingHeaderBottom}>
|
|
386
386
|
<View style={styles.headerTitleWithIcon}>
|
|
387
|
-
<OxyIcon
|
|
388
|
-
name={getFieldIcon(editingField).name}
|
|
389
|
-
size={50}
|
|
390
|
-
color={getFieldIcon(editingField).color}
|
|
391
|
-
style={styles.headerIcon}
|
|
387
|
+
<OxyIcon
|
|
388
|
+
name={getFieldIcon(editingField).name}
|
|
389
|
+
size={50}
|
|
390
|
+
color={getFieldIcon(editingField).color}
|
|
391
|
+
style={styles.headerIcon}
|
|
392
392
|
/>
|
|
393
393
|
<Text style={styles.headerTitleLarge}>{getFieldLabel(editingField)}</Text>
|
|
394
394
|
</View>
|
|
@@ -401,8 +401,8 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
401
401
|
</TouchableOpacity>
|
|
402
402
|
<Text style={styles.headerTitle}>Account Settings</Text>
|
|
403
403
|
<Animated.View style={{ transform: [{ scale: saveButtonScale }] }}>
|
|
404
|
-
<TouchableOpacity
|
|
405
|
-
style={[styles.saveIconButton, { opacity: isSaving ? 0.7 : 1 }]}
|
|
404
|
+
<TouchableOpacity
|
|
405
|
+
style={[styles.saveIconButton, { opacity: isSaving ? 0.7 : 1 }]}
|
|
406
406
|
onPress={handleSave}
|
|
407
407
|
disabled={isSaving}
|
|
408
408
|
>
|
|
@@ -429,8 +429,8 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
429
429
|
{/* Profile Picture Section */}
|
|
430
430
|
<View style={styles.section}>
|
|
431
431
|
<Text style={styles.sectionTitle}>Profile Picture</Text>
|
|
432
|
-
|
|
433
|
-
<TouchableOpacity
|
|
432
|
+
|
|
433
|
+
<TouchableOpacity
|
|
434
434
|
style={[styles.settingItem, styles.firstSettingItem, styles.lastSettingItem]}
|
|
435
435
|
onPress={handleAvatarUpdate}
|
|
436
436
|
>
|
|
@@ -457,7 +457,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
457
457
|
{/* Basic Information */}
|
|
458
458
|
<View style={styles.section}>
|
|
459
459
|
<Text style={styles.sectionTitle}>Basic Information</Text>
|
|
460
|
-
|
|
460
|
+
|
|
461
461
|
{renderField(
|
|
462
462
|
'displayName',
|
|
463
463
|
'Display Name',
|
|
@@ -501,7 +501,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
501
501
|
{/* About You */}
|
|
502
502
|
<View style={styles.section}>
|
|
503
503
|
<Text style={styles.sectionTitle}>About You</Text>
|
|
504
|
-
|
|
504
|
+
|
|
505
505
|
{renderField(
|
|
506
506
|
'bio',
|
|
507
507
|
'Bio',
|
|
@@ -545,8 +545,8 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
545
545
|
{/* Quick Actions */}
|
|
546
546
|
<View style={styles.section}>
|
|
547
547
|
<Text style={styles.sectionTitle}>Quick Actions</Text>
|
|
548
|
-
|
|
549
|
-
<TouchableOpacity
|
|
548
|
+
|
|
549
|
+
<TouchableOpacity
|
|
550
550
|
style={[styles.settingItem, styles.firstSettingItem]}
|
|
551
551
|
onPress={() => toast.info('Privacy settings coming soon!')}
|
|
552
552
|
>
|
|
@@ -560,7 +560,7 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
560
560
|
<OxyIcon name="chevron-forward" size={16} color="#ccc" />
|
|
561
561
|
</TouchableOpacity>
|
|
562
562
|
|
|
563
|
-
<TouchableOpacity
|
|
563
|
+
<TouchableOpacity
|
|
564
564
|
style={[styles.settingItem, styles.lastSettingItem]}
|
|
565
565
|
onPress={() => toast.info('Account verification coming soon!')}
|
|
566
566
|
>
|