@oxyhq/services 5.8.1 → 5.8.3
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/index.js +9 -27
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/node/createAuth.js +7 -585
- package/lib/commonjs/node/createAuth.js.map +1 -1
- package/lib/commonjs/node/index.js +1 -38
- package/lib/commonjs/node/index.js.map +1 -1
- package/lib/commonjs/ui/components/FollowButton.js +100 -12
- package/lib/commonjs/ui/components/FollowButton.js.map +1 -1
- package/lib/commonjs/ui/components/Header.js +40 -6
- package/lib/commonjs/ui/components/Header.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +5 -0
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +63 -125
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/index.js +6 -0
- package/lib/commonjs/ui/hooks/index.js.map +1 -1
- package/lib/commonjs/ui/hooks/useFollow.js +59 -2
- package/lib/commonjs/ui/hooks/useFollow.js.map +1 -1
- package/lib/commonjs/ui/navigation/OxyRouter.js +10 -0
- package/lib/commonjs/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +9 -0
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js +214 -37
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/UserLinksScreen.js +90 -0
- package/lib/commonjs/ui/screens/UserLinksScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js +9 -6
- package/lib/commonjs/ui/screens/karma/KarmaAboutScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js +3 -30
- package/lib/commonjs/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js +37 -46
- package/lib/commonjs/ui/screens/karma/KarmaFAQScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js +9 -12
- package/lib/commonjs/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js +9 -12
- package/lib/commonjs/ui/screens/karma/KarmaRewardsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js +9 -12
- package/lib/commonjs/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
- package/lib/commonjs/ui/stores/authStore.js +24 -6
- package/lib/commonjs/ui/stores/authStore.js.map +1 -1
- package/lib/commonjs/ui/stores/followStore.js +106 -1
- package/lib/commonjs/ui/stores/followStore.js.map +1 -1
- package/lib/module/index.js +1 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/node/createAuth.js +7 -584
- package/lib/module/node/createAuth.js.map +1 -1
- package/lib/module/node/index.js +1 -7
- package/lib/module/node/index.js.map +1 -1
- package/lib/module/ui/components/FollowButton.js +101 -13
- package/lib/module/ui/components/FollowButton.js.map +1 -1
- package/lib/module/ui/components/Header.js +40 -6
- package/lib/module/ui/components/Header.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +5 -0
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +63 -125
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/index.js +1 -1
- package/lib/module/ui/hooks/index.js.map +1 -1
- package/lib/module/ui/hooks/useFollow.js +57 -1
- package/lib/module/ui/hooks/useFollow.js.map +1 -1
- package/lib/module/ui/navigation/OxyRouter.js +10 -0
- package/lib/module/ui/navigation/OxyRouter.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +9 -0
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +214 -37
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/UserLinksScreen.js +85 -0
- package/lib/module/ui/screens/UserLinksScreen.js.map +1 -0
- package/lib/module/ui/screens/karma/KarmaAboutScreen.js +9 -6
- package/lib/module/ui/screens/karma/KarmaAboutScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js +3 -30
- package/lib/module/ui/screens/karma/KarmaCenterScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaFAQScreen.js +37 -46
- package/lib/module/ui/screens/karma/KarmaFAQScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js +9 -12
- package/lib/module/ui/screens/karma/KarmaLeaderboardScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaRewardsScreen.js +9 -12
- package/lib/module/ui/screens/karma/KarmaRewardsScreen.js.map +1 -1
- package/lib/module/ui/screens/karma/KarmaRulesScreen.js +9 -12
- package/lib/module/ui/screens/karma/KarmaRulesScreen.js.map +1 -1
- package/lib/module/ui/stores/authStore.js +24 -6
- package/lib/module/ui/stores/authStore.js.map +1 -1
- package/lib/module/ui/stores/followStore.js +106 -1
- package/lib/module/ui/stores/followStore.js.map +1 -1
- package/lib/typescript/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/node/createAuth.d.ts +0 -112
- package/lib/typescript/node/createAuth.d.ts.map +1 -1
- package/lib/typescript/node/index.d.ts +0 -2
- package/lib/typescript/node/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/components/Header.d.ts +2 -0
- package/lib/typescript/ui/components/Header.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/index.d.ts +1 -1
- package/lib/typescript/ui/hooks/index.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useFollow.d.ts +20 -0
- package/lib/typescript/ui/hooks/useFollow.d.ts.map +1 -1
- package/lib/typescript/ui/navigation/OxyRouter.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/UserLinksScreen.d.ts +15 -0
- package/lib/typescript/ui/screens/UserLinksScreen.d.ts.map +1 -0
- package/lib/typescript/ui/screens/karma/KarmaAboutScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaCenterScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaFAQScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaLeaderboardScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaRewardsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/karma/KarmaRulesScreen.d.ts.map +1 -1
- package/lib/typescript/ui/stores/authStore.d.ts +3 -1
- package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
- package/lib/typescript/ui/stores/followStore.d.ts +10 -0
- package/lib/typescript/ui/stores/followStore.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/index.ts +2 -10
- package/src/node/createAuth.ts +7 -623
- package/src/node/index.ts +1 -19
- package/src/ui/components/FollowButton.tsx +95 -11
- package/src/ui/components/Header.tsx +45 -4
- package/src/ui/components/OxyProvider.tsx +6 -0
- package/src/ui/context/OxyContext.tsx +65 -136
- package/src/ui/hooks/index.ts +1 -1
- package/src/ui/hooks/useFollow.ts +63 -0
- package/src/ui/navigation/OxyRouter.tsx +10 -0
- package/src/ui/screens/AccountSettingsScreen.tsx +8 -0
- package/src/ui/screens/ProfileScreen.tsx +191 -28
- package/src/ui/screens/UserLinksScreen.tsx +96 -0
- package/src/ui/screens/karma/KarmaAboutScreen.tsx +9 -2
- package/src/ui/screens/karma/KarmaCenterScreen.tsx +1 -20
- package/src/ui/screens/karma/KarmaFAQScreen.tsx +40 -24
- package/src/ui/screens/karma/KarmaLeaderboardScreen.tsx +9 -3
- package/src/ui/screens/karma/KarmaRewardsScreen.tsx +9 -3
- package/src/ui/screens/karma/KarmaRulesScreen.tsx +9 -3
- package/src/ui/stores/authStore.ts +22 -7
- package/src/ui/stores/followStore.ts +102 -1
|
@@ -14,6 +14,11 @@ export const useFollow = (userId?: string | string[]) => {
|
|
|
14
14
|
const isFollowing = isSingleUser && userId ? followState.followingUsers[userId] ?? false : false;
|
|
15
15
|
const isLoading = isSingleUser && userId ? followState.loadingUsers[userId] ?? false : false;
|
|
16
16
|
const error = isSingleUser && userId ? followState.errors[userId] ?? null : null;
|
|
17
|
+
|
|
18
|
+
// Follower count helpers
|
|
19
|
+
const followerCount = isSingleUser && userId ? followState.followerCounts[userId] ?? null : null;
|
|
20
|
+
const followingCount = isSingleUser && userId ? followState.followingCounts[userId] ?? null : null;
|
|
21
|
+
const isLoadingCounts = isSingleUser && userId ? followState.loadingCounts[userId] ?? false : false;
|
|
17
22
|
|
|
18
23
|
const toggleFollow = useCallback(async () => {
|
|
19
24
|
if (!isSingleUser || !userId) throw new Error('toggleFollow is only available for single user mode');
|
|
@@ -35,6 +40,21 @@ export const useFollow = (userId?: string | string[]) => {
|
|
|
35
40
|
followState.clearFollowError(userId);
|
|
36
41
|
}, [isSingleUser, userId, followState]);
|
|
37
42
|
|
|
43
|
+
const fetchUserCounts = useCallback(async () => {
|
|
44
|
+
if (!isSingleUser || !userId) throw new Error('fetchUserCounts is only available for single user mode');
|
|
45
|
+
await followState.fetchUserCounts(userId, oxyServices);
|
|
46
|
+
}, [isSingleUser, userId, followState, oxyServices]);
|
|
47
|
+
|
|
48
|
+
const setFollowerCount = useCallback((count: number) => {
|
|
49
|
+
if (!isSingleUser || !userId) throw new Error('setFollowerCount is only available for single user mode');
|
|
50
|
+
followState.setFollowerCount(userId, count);
|
|
51
|
+
}, [isSingleUser, userId, followState]);
|
|
52
|
+
|
|
53
|
+
const setFollowingCount = useCallback((count: number) => {
|
|
54
|
+
if (!isSingleUser || !userId) throw new Error('setFollowingCount is only available for single user mode');
|
|
55
|
+
followState.setFollowingCount(userId, count);
|
|
56
|
+
}, [isSingleUser, userId, followState]);
|
|
57
|
+
|
|
38
58
|
// Multiple user helpers
|
|
39
59
|
const followData = useMemo(() => {
|
|
40
60
|
const data: Record<string, { isFollowing: boolean; isLoading: boolean; error: string | null }> = {};
|
|
@@ -69,6 +89,11 @@ export const useFollow = (userId?: string | string[]) => {
|
|
|
69
89
|
followState.clearFollowError(targetUserId);
|
|
70
90
|
}, [followState]);
|
|
71
91
|
|
|
92
|
+
const updateCountsFromFollowAction = useCallback((targetUserId: string, action: 'follow' | 'unfollow', counts: { followers: number; following: number }) => {
|
|
93
|
+
const currentUserId = oxyServices.getCurrentUserId() || undefined;
|
|
94
|
+
followState.updateCountsFromFollowAction(targetUserId, action, counts, currentUserId);
|
|
95
|
+
}, [followState, oxyServices]);
|
|
96
|
+
|
|
72
97
|
// Aggregate helpers for multiple users
|
|
73
98
|
const isAnyLoading = userIds.some(uid => followState.loadingUsers[uid]);
|
|
74
99
|
const hasAnyError = userIds.some(uid => !!followState.errors[uid]);
|
|
@@ -84,6 +109,13 @@ export const useFollow = (userId?: string | string[]) => {
|
|
|
84
109
|
setFollowStatus,
|
|
85
110
|
fetchStatus,
|
|
86
111
|
clearError,
|
|
112
|
+
// Follower count methods
|
|
113
|
+
followerCount,
|
|
114
|
+
followingCount,
|
|
115
|
+
isLoadingCounts,
|
|
116
|
+
fetchUserCounts,
|
|
117
|
+
setFollowerCount,
|
|
118
|
+
setFollowingCount,
|
|
87
119
|
};
|
|
88
120
|
}
|
|
89
121
|
|
|
@@ -99,4 +131,35 @@ export const useFollow = (userId?: string | string[]) => {
|
|
|
99
131
|
allFollowing,
|
|
100
132
|
allNotFollowing,
|
|
101
133
|
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Convenience hook for just follower counts
|
|
137
|
+
export const useFollowerCounts = (userId: string) => {
|
|
138
|
+
const { oxyServices } = useOxy();
|
|
139
|
+
const followState = useFollowStore();
|
|
140
|
+
|
|
141
|
+
const followerCount = followState.followerCounts[userId] ?? null;
|
|
142
|
+
const followingCount = followState.followingCounts[userId] ?? null;
|
|
143
|
+
const isLoadingCounts = followState.loadingCounts[userId] ?? false;
|
|
144
|
+
|
|
145
|
+
const fetchUserCounts = useCallback(async () => {
|
|
146
|
+
await followState.fetchUserCounts(userId, oxyServices);
|
|
147
|
+
}, [userId, followState, oxyServices]);
|
|
148
|
+
|
|
149
|
+
const setFollowerCount = useCallback((count: number) => {
|
|
150
|
+
followState.setFollowerCount(userId, count);
|
|
151
|
+
}, [userId, followState]);
|
|
152
|
+
|
|
153
|
+
const setFollowingCount = useCallback((count: number) => {
|
|
154
|
+
followState.setFollowingCount(userId, count);
|
|
155
|
+
}, [userId, followState]);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
followerCount,
|
|
159
|
+
followingCount,
|
|
160
|
+
isLoadingCounts,
|
|
161
|
+
fetchUserCounts,
|
|
162
|
+
setFollowerCount,
|
|
163
|
+
setFollowingCount,
|
|
164
|
+
};
|
|
102
165
|
};
|
|
@@ -20,6 +20,7 @@ import KarmaAboutScreen from '../screens/karma/KarmaAboutScreen';
|
|
|
20
20
|
import KarmaRewardsScreen from '../screens/karma/KarmaRewardsScreen';
|
|
21
21
|
import KarmaFAQScreen from '../screens/karma/KarmaFAQScreen';
|
|
22
22
|
import ProfileScreen from '../screens/ProfileScreen';
|
|
23
|
+
import UserLinksScreen from '../screens/UserLinksScreen';
|
|
23
24
|
import FileManagementScreen from '../screens/FileManagementScreen';
|
|
24
25
|
import RecoverAccountScreen from '../screens/RecoverAccountScreen';
|
|
25
26
|
import PaymentGatewayScreen from '../screens/PaymentGatewayScreen';
|
|
@@ -101,6 +102,10 @@ const routes: Record<string, RouteConfig> = {
|
|
|
101
102
|
component: ProfileScreen,
|
|
102
103
|
snapPoints: ['60%', '90%'],
|
|
103
104
|
},
|
|
105
|
+
UserLinks: {
|
|
106
|
+
component: UserLinksScreen,
|
|
107
|
+
snapPoints: ['60%', '90%'],
|
|
108
|
+
},
|
|
104
109
|
FileManagement: {
|
|
105
110
|
component: FileManagementScreen,
|
|
106
111
|
snapPoints: ['70%', '100%'],
|
|
@@ -135,11 +140,14 @@ const OxyRouter: React.FC<OxyRouterProps> = ({
|
|
|
135
140
|
|
|
136
141
|
// Memoized navigation methods
|
|
137
142
|
const navigate = useCallback((screen: string, props: Record<string, any> = {}) => {
|
|
143
|
+
console.log('OxyRouter: navigate called with screen:', screen, 'props:', props);
|
|
138
144
|
if (routes[screen]) {
|
|
145
|
+
console.log('OxyRouter: screen found in routes, navigating to:', screen);
|
|
139
146
|
setCurrentScreen(screen);
|
|
140
147
|
setScreenHistory(prev => [...prev, screen]);
|
|
141
148
|
setScreenPropsMap(prev => ({ ...prev, [screen]: props }));
|
|
142
149
|
} else {
|
|
150
|
+
console.error(`OxyRouter: Screen "${screen}" not found in routes:`, Object.keys(routes));
|
|
143
151
|
if (process.env.NODE_ENV !== 'production') {
|
|
144
152
|
console.error(`Screen "${screen}" not found`);
|
|
145
153
|
}
|
|
@@ -165,10 +173,12 @@ const OxyRouter: React.FC<OxyRouterProps> = ({
|
|
|
165
173
|
useEffect(() => {
|
|
166
174
|
if (navigationRef) {
|
|
167
175
|
navigationRef.current = navigate;
|
|
176
|
+
console.log('OxyRouter: navigationRef.current set to navigate function');
|
|
168
177
|
}
|
|
169
178
|
return () => {
|
|
170
179
|
if (navigationRef) {
|
|
171
180
|
navigationRef.current = null;
|
|
181
|
+
console.log('OxyRouter: navigationRef.current cleared');
|
|
172
182
|
}
|
|
173
183
|
};
|
|
174
184
|
}, [navigate, navigationRef]);
|
|
@@ -1019,6 +1019,14 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
|
|
|
1019
1019
|
|
|
1020
1020
|
<GroupedSection
|
|
1021
1021
|
items={[
|
|
1022
|
+
{
|
|
1023
|
+
id: 'preview-profile',
|
|
1024
|
+
icon: 'eye',
|
|
1025
|
+
iconColor: '#007AFF',
|
|
1026
|
+
title: 'Preview Profile',
|
|
1027
|
+
subtitle: 'See how your profile looks to others',
|
|
1028
|
+
onPress: () => navigate?.('Profile', { userId: user?.id }),
|
|
1029
|
+
},
|
|
1022
1030
|
{
|
|
1023
1031
|
id: 'privacy-settings',
|
|
1024
1032
|
icon: 'shield-checkmark',
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
|
-
import { View, Text, StyleSheet, ActivityIndicator, ScrollView, TouchableOpacity } from 'react-native';
|
|
2
|
+
import { View, Text, StyleSheet, ActivityIndicator, ScrollView, TouchableOpacity, Image } from 'react-native';
|
|
3
3
|
import { BaseScreenProps } from '../navigation/types';
|
|
4
4
|
import { useOxy } from '../context/OxyContext';
|
|
5
5
|
import Avatar from '../components/Avatar';
|
|
6
|
+
import { FollowButton } from '../components';
|
|
7
|
+
import { useFollow } from '../hooks/useFollow';
|
|
6
8
|
import { Ionicons } from '@expo/vector-icons';
|
|
7
9
|
|
|
8
10
|
interface ProfileScreenProps extends BaseScreenProps {
|
|
@@ -10,22 +12,40 @@ interface ProfileScreenProps extends BaseScreenProps {
|
|
|
10
12
|
username?: string;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme, goBack }) => {
|
|
15
|
+
const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme, goBack, navigate }) => {
|
|
14
16
|
const { oxyServices, user: currentUser } = useOxy();
|
|
15
17
|
const [profile, setProfile] = useState<any>(null);
|
|
16
18
|
const [karmaTotal, setKarmaTotal] = useState<number | null>(null);
|
|
17
19
|
const [postsCount, setPostsCount] = useState<number | null>(null);
|
|
18
20
|
const [commentsCount, setCommentsCount] = useState<number | null>(null);
|
|
19
|
-
const [followersCount, setFollowersCount] = useState<number | null>(null);
|
|
20
|
-
const [followingCount, setFollowingCount] = useState<number | null>(null);
|
|
21
21
|
const [isLoading, setIsLoading] = useState(true);
|
|
22
22
|
const [error, setError] = useState<string | null>(null);
|
|
23
|
+
const [links, setLinks] = useState<Array<{
|
|
24
|
+
url: string;
|
|
25
|
+
title?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
image?: string;
|
|
28
|
+
id: string;
|
|
29
|
+
}>>([]);
|
|
30
|
+
|
|
31
|
+
// Use the follow hook for real follower data
|
|
32
|
+
const {
|
|
33
|
+
followerCount,
|
|
34
|
+
followingCount,
|
|
35
|
+
isLoadingCounts,
|
|
36
|
+
fetchUserCounts,
|
|
37
|
+
setFollowerCount,
|
|
38
|
+
setFollowingCount,
|
|
39
|
+
} = useFollow(userId);
|
|
23
40
|
|
|
24
41
|
const isDarkTheme = theme === 'dark';
|
|
25
42
|
const backgroundColor = isDarkTheme ? '#121212' : '#FFFFFF';
|
|
26
43
|
const textColor = isDarkTheme ? '#FFFFFF' : '#000000';
|
|
27
44
|
const primaryColor = '#d169e5';
|
|
28
45
|
|
|
46
|
+
// Check if current user is viewing their own profile
|
|
47
|
+
const isOwnProfile = currentUser && currentUser.id === userId;
|
|
48
|
+
|
|
29
49
|
useEffect(() => {
|
|
30
50
|
console.log('ProfileScreen - userId:', userId);
|
|
31
51
|
console.log('ProfileScreen - username:', username);
|
|
@@ -62,12 +82,50 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
|
|
|
62
82
|
setProfile(profileRes);
|
|
63
83
|
setKarmaTotal(typeof karmaRes.total === 'number' ? karmaRes.total : null);
|
|
64
84
|
|
|
65
|
-
//
|
|
66
|
-
|
|
85
|
+
// Extract links from profile data
|
|
86
|
+
if (profileRes.linksMetadata && Array.isArray(profileRes.linksMetadata)) {
|
|
87
|
+
const linksWithIds = profileRes.linksMetadata.map((link: any, index: number) => ({
|
|
88
|
+
...link,
|
|
89
|
+
id: link.id || `existing-${index}`
|
|
90
|
+
}));
|
|
91
|
+
setLinks(linksWithIds);
|
|
92
|
+
} else if (Array.isArray(profileRes.links)) {
|
|
93
|
+
const simpleLinks = profileRes.links.map((l: any) => typeof l === 'string' ? l : l.link).filter(Boolean);
|
|
94
|
+
const linksWithMetadata = simpleLinks.map((url: string, index: number) => ({
|
|
95
|
+
url,
|
|
96
|
+
title: url.replace(/^https?:\/\//, '').replace(/\/$/, ''),
|
|
97
|
+
description: `Link to ${url}`,
|
|
98
|
+
image: undefined,
|
|
99
|
+
id: `existing-${index}`
|
|
100
|
+
}));
|
|
101
|
+
setLinks(linksWithMetadata);
|
|
102
|
+
} else if (profileRes.website) {
|
|
103
|
+
setLinks([{
|
|
104
|
+
url: profileRes.website,
|
|
105
|
+
title: profileRes.website.replace(/^https?:\/\//, '').replace(/\/$/, ''),
|
|
106
|
+
description: `Link to ${profileRes.website}`,
|
|
107
|
+
image: undefined,
|
|
108
|
+
id: 'existing-0'
|
|
109
|
+
}]);
|
|
110
|
+
} else {
|
|
111
|
+
setLinks([]);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Set real follower counts from profile data if available
|
|
115
|
+
if (profileRes._count) {
|
|
116
|
+
setFollowerCount?.(profileRes._count.followers || 0);
|
|
117
|
+
setFollowingCount?.(profileRes._count.following || 0);
|
|
118
|
+
} else if (profileRes.stats) {
|
|
119
|
+
setFollowerCount?.(profileRes.stats.followers || 0);
|
|
120
|
+
setFollowingCount?.(profileRes.stats.following || 0);
|
|
121
|
+
} else {
|
|
122
|
+
// Fallback: fetch counts separately
|
|
123
|
+
fetchUserCounts?.();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Mock data for other stats (these would come from separate API endpoints)
|
|
67
127
|
setPostsCount(Math.floor(Math.random() * 50));
|
|
68
128
|
setCommentsCount(Math.floor(Math.random() * 100));
|
|
69
|
-
setFollowersCount(Math.floor(Math.random() * 200));
|
|
70
|
-
setFollowingCount(Math.floor(Math.random() * 100));
|
|
71
129
|
})
|
|
72
130
|
.catch((err: any) => {
|
|
73
131
|
console.error('Profile loading error:', err);
|
|
@@ -135,9 +193,25 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
|
|
|
135
193
|
<View style={styles.avatarWrapper}>
|
|
136
194
|
<Avatar uri={profile?.avatar?.url} name={profile?.username || username} size={96} theme={theme} />
|
|
137
195
|
</View>
|
|
138
|
-
{/*
|
|
196
|
+
{/* Conditional Action Button */}
|
|
139
197
|
<View style={styles.actionButtonWrapper}>
|
|
140
|
-
|
|
198
|
+
{isOwnProfile ? (
|
|
199
|
+
<TouchableOpacity
|
|
200
|
+
style={styles.actionButton}
|
|
201
|
+
onPress={() => navigate?.('EditProfile')}
|
|
202
|
+
>
|
|
203
|
+
<Text style={styles.actionButtonText}>Edit Profile</Text>
|
|
204
|
+
</TouchableOpacity>
|
|
205
|
+
) : (
|
|
206
|
+
<FollowButton
|
|
207
|
+
userId={userId}
|
|
208
|
+
theme={theme}
|
|
209
|
+
onFollowChange={(isFollowing) => {
|
|
210
|
+
// The follow button will automatically update counts via Zustand
|
|
211
|
+
console.log(`Follow status changed: ${isFollowing}`);
|
|
212
|
+
}}
|
|
213
|
+
/>
|
|
214
|
+
)}
|
|
141
215
|
</View>
|
|
142
216
|
</View>
|
|
143
217
|
{/* Profile Info */}
|
|
@@ -148,20 +222,67 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
|
|
|
148
222
|
)}
|
|
149
223
|
{/* Bio placeholder */}
|
|
150
224
|
<Text style={[styles.bio, { color: textColor }]}>{profile?.bio || 'This user has no bio yet.'}</Text>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
<
|
|
225
|
+
|
|
226
|
+
{/* Info Grid Row */}
|
|
227
|
+
<View style={styles.infoGrid}>
|
|
228
|
+
{profile?.createdAt && (
|
|
229
|
+
<View style={styles.infoGridItem}>
|
|
230
|
+
<Ionicons name="calendar-outline" size={16} color={isDarkTheme ? '#BBBBBB' : '#888888'} style={{ marginRight: 6 }} />
|
|
231
|
+
<Text style={[styles.infoGridText, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]}>Joined {new Date(profile.createdAt).toLocaleDateString()}</Text>
|
|
157
232
|
</View>
|
|
158
233
|
)}
|
|
159
|
-
{profile?.
|
|
160
|
-
<View style={styles.
|
|
161
|
-
<Ionicons name="
|
|
162
|
-
<Text style={[styles.
|
|
234
|
+
{profile?.location && (
|
|
235
|
+
<View style={styles.infoGridItem}>
|
|
236
|
+
<Ionicons name="location-outline" size={16} color={isDarkTheme ? '#BBBBBB' : '#888888'} style={{ marginRight: 6 }} />
|
|
237
|
+
<Text style={[styles.infoGridText, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]} numberOfLines={1}>{profile.location}</Text>
|
|
238
|
+
</View>
|
|
239
|
+
)}
|
|
240
|
+
{profile?.website && (
|
|
241
|
+
<View style={styles.infoGridItem}>
|
|
242
|
+
<Ionicons name="globe-outline" size={16} color={isDarkTheme ? '#BBBBBB' : '#888888'} style={{ marginRight: 6 }} />
|
|
243
|
+
<Text style={[styles.infoGridText, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]} numberOfLines={1}>{profile.website}</Text>
|
|
244
|
+
</View>
|
|
245
|
+
)}
|
|
246
|
+
{profile?.company && (
|
|
247
|
+
<View style={styles.infoGridItem}>
|
|
248
|
+
<Ionicons name="business-outline" size={16} color={isDarkTheme ? '#BBBBBB' : '#888888'} style={{ marginRight: 6 }} />
|
|
249
|
+
<Text style={[styles.infoGridText, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]} numberOfLines={1}>{profile.company}</Text>
|
|
250
|
+
</View>
|
|
251
|
+
)}
|
|
252
|
+
{profile?.jobTitle && (
|
|
253
|
+
<View style={styles.infoGridItem}>
|
|
254
|
+
<Ionicons name="briefcase-outline" size={16} color={isDarkTheme ? '#BBBBBB' : '#888888'} style={{ marginRight: 6 }} />
|
|
255
|
+
<Text style={[styles.infoGridText, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]} numberOfLines={1}>{profile.jobTitle}</Text>
|
|
163
256
|
</View>
|
|
164
257
|
)}
|
|
258
|
+
{profile?.education && (
|
|
259
|
+
<View style={styles.infoGridItem}>
|
|
260
|
+
<Ionicons name="school-outline" size={16} color={isDarkTheme ? '#BBBBBB' : '#888888'} style={{ marginRight: 6 }} />
|
|
261
|
+
<Text style={[styles.infoGridText, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]} numberOfLines={1}>{profile.education}</Text>
|
|
262
|
+
</View>
|
|
263
|
+
)}
|
|
264
|
+
{profile?.birthday && (
|
|
265
|
+
<View style={styles.infoGridItem}>
|
|
266
|
+
<Ionicons name="gift-outline" size={16} color={isDarkTheme ? '#BBBBBB' : '#888888'} style={{ marginRight: 6 }} />
|
|
267
|
+
<Text style={[styles.infoGridText, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]}>Born {new Date(profile.birthday).toLocaleDateString()}</Text>
|
|
268
|
+
</View>
|
|
269
|
+
)}
|
|
270
|
+
{links.length > 0 && (
|
|
271
|
+
<TouchableOpacity
|
|
272
|
+
style={styles.infoGridItem}
|
|
273
|
+
onPress={() => navigate?.('UserLinks', { userId, links })}
|
|
274
|
+
>
|
|
275
|
+
<Ionicons name="link-outline" size={16} color={isDarkTheme ? '#BBBBBB' : '#888888'} style={{ marginRight: 6 }} />
|
|
276
|
+
<Text style={[styles.infoGridText, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]} numberOfLines={1}>
|
|
277
|
+
{links[0].url}
|
|
278
|
+
</Text>
|
|
279
|
+
{links.length > 1 && (
|
|
280
|
+
<Text style={[styles.linksMore, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]}>
|
|
281
|
+
+ {links.length - 1} more
|
|
282
|
+
</Text>
|
|
283
|
+
)}
|
|
284
|
+
</TouchableOpacity>
|
|
285
|
+
)}
|
|
165
286
|
</View>
|
|
166
287
|
{/* Divider */}
|
|
167
288
|
<View style={styles.divider} />
|
|
@@ -172,11 +293,19 @@ const ProfileScreen: React.FC<ProfileScreenProps> = ({ userId, username, theme,
|
|
|
172
293
|
<Text style={[styles.karmaLabel, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]}>Karma</Text>
|
|
173
294
|
</View>
|
|
174
295
|
<View style={styles.statItem}>
|
|
175
|
-
|
|
296
|
+
{isLoadingCounts ? (
|
|
297
|
+
<ActivityIndicator size="small" color={textColor} />
|
|
298
|
+
) : (
|
|
299
|
+
<Text style={[styles.karmaAmount, { color: textColor }]}>{followerCount !== null ? followerCount : '--'}</Text>
|
|
300
|
+
)}
|
|
176
301
|
<Text style={[styles.karmaLabel, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]}>Followers</Text>
|
|
177
302
|
</View>
|
|
178
303
|
<View style={styles.statItem}>
|
|
179
|
-
|
|
304
|
+
{isLoadingCounts ? (
|
|
305
|
+
<ActivityIndicator size="small" color={textColor} />
|
|
306
|
+
) : (
|
|
307
|
+
<Text style={[styles.karmaAmount, { color: textColor }]}>{followingCount !== null ? followingCount : '--'}</Text>
|
|
308
|
+
)}
|
|
180
309
|
<Text style={[styles.karmaLabel, { color: isDarkTheme ? '#BBBBBB' : '#888888' }]}>Following</Text>
|
|
181
310
|
</View>
|
|
182
311
|
</View>
|
|
@@ -194,15 +323,49 @@ const styles = StyleSheet.create({
|
|
|
194
323
|
avatarRow: { flexDirection: 'row', alignItems: 'flex-end', marginTop: -56, paddingHorizontal: 20, justifyContent: 'space-between', zIndex: 2 },
|
|
195
324
|
avatarWrapper: { borderWidth: 5, borderColor: '#fff', borderRadius: 64, overflow: 'hidden', backgroundColor: '#fff', },
|
|
196
325
|
actionButtonWrapper: { flex: 1, alignItems: 'flex-end', justifyContent: 'flex-end' },
|
|
197
|
-
actionButton: {
|
|
198
|
-
|
|
326
|
+
actionButton: {
|
|
327
|
+
backgroundColor: '#fff',
|
|
328
|
+
borderWidth: 1,
|
|
329
|
+
borderColor: '#d169e5',
|
|
330
|
+
borderRadius: 24,
|
|
331
|
+
paddingVertical: 7,
|
|
332
|
+
paddingHorizontal: 22,
|
|
333
|
+
marginBottom: 8,
|
|
334
|
+
elevation: 2,
|
|
335
|
+
shadowColor: '#d169e5',
|
|
336
|
+
shadowOffset: { width: 0, height: 1 },
|
|
337
|
+
shadowOpacity: 0.08,
|
|
338
|
+
shadowRadius: 2
|
|
339
|
+
},
|
|
340
|
+
actionButtonText: {
|
|
341
|
+
color: '#d169e5',
|
|
342
|
+
fontWeight: 'bold',
|
|
343
|
+
fontSize: 16
|
|
344
|
+
},
|
|
345
|
+
header: { alignItems: 'flex-start', width: '100%', paddingHorizontal: 20 },
|
|
199
346
|
displayName: { fontSize: 24, fontWeight: 'bold', marginTop: 10, marginBottom: 2, letterSpacing: 0.1 },
|
|
200
347
|
subText: { fontSize: 16, marginBottom: 2, color: '#a0a0a0' },
|
|
201
|
-
bio: { fontSize: 16, marginTop: 10, marginBottom: 10, color: '#666',
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
348
|
+
bio: { fontSize: 16, marginTop: 10, marginBottom: 10, color: '#666', lineHeight: 22 },
|
|
349
|
+
infoGrid: {
|
|
350
|
+
flexDirection: 'row',
|
|
351
|
+
alignItems: 'center',
|
|
352
|
+
marginBottom: 10,
|
|
353
|
+
flexWrap: 'wrap'
|
|
354
|
+
},
|
|
355
|
+
infoGridItem: {
|
|
356
|
+
flexDirection: 'row',
|
|
357
|
+
alignItems: 'center',
|
|
358
|
+
marginRight: 24,
|
|
359
|
+
marginBottom: 4
|
|
360
|
+
},
|
|
361
|
+
infoGridText: {
|
|
362
|
+
fontSize: 15
|
|
363
|
+
},
|
|
205
364
|
divider: { height: 1, backgroundColor: '#e0e0e0', width: '100%', marginVertical: 14 },
|
|
365
|
+
linksMore: {
|
|
366
|
+
fontSize: 15,
|
|
367
|
+
marginLeft: 4
|
|
368
|
+
},
|
|
206
369
|
statsRow: { width: '100%', flex: 1, flexDirection: 'row', alignItems: 'center', marginTop: 6, marginBottom: 2, justifyContent: 'space-between' },
|
|
207
370
|
statItem: { flex: 1, alignItems: 'center', minWidth: 50, marginBottom: 12 },
|
|
208
371
|
karmaLabel: { fontSize: 14, marginBottom: 2, textAlign: 'center', color: '#a0a0a0' },
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, Text, StyleSheet, ScrollView, TouchableOpacity, Image, Linking } from 'react-native';
|
|
3
|
+
import { Ionicons } from '@expo/vector-icons';
|
|
4
|
+
import { BaseScreenProps } from '../navigation/types';
|
|
5
|
+
import { Header, GroupedSection } from '../components';
|
|
6
|
+
|
|
7
|
+
interface UserLinksScreenProps extends BaseScreenProps {
|
|
8
|
+
userId: string;
|
|
9
|
+
links: Array<{
|
|
10
|
+
url: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
image?: string;
|
|
14
|
+
id: string;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const UserLinksScreen: React.FC<UserLinksScreenProps> = ({
|
|
19
|
+
userId,
|
|
20
|
+
links,
|
|
21
|
+
theme,
|
|
22
|
+
goBack,
|
|
23
|
+
navigate
|
|
24
|
+
}) => {
|
|
25
|
+
const isDarkTheme = theme === 'dark';
|
|
26
|
+
const themeStyles = {
|
|
27
|
+
backgroundColor: isDarkTheme ? '#000' : '#f2f2f2',
|
|
28
|
+
textColor: isDarkTheme ? '#fff' : '#333',
|
|
29
|
+
primaryColor: '#007AFF',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const handleLinkPress = async (url: string) => {
|
|
33
|
+
try {
|
|
34
|
+
await Linking.openURL(url);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error('Error opening link:', error);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const groupedItems = links.map((link) => ({
|
|
41
|
+
id: link.id,
|
|
42
|
+
icon: link.image ? undefined : 'link',
|
|
43
|
+
iconColor: '#32D74B',
|
|
44
|
+
image: link.image || undefined,
|
|
45
|
+
imageSize: 40,
|
|
46
|
+
title: link.title || link.url,
|
|
47
|
+
subtitle: link.description || link.url,
|
|
48
|
+
onPress: () => handleLinkPress(link.url),
|
|
49
|
+
multiRow: true,
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<View style={[styles.container, { backgroundColor: themeStyles.backgroundColor }]}>
|
|
54
|
+
<Header
|
|
55
|
+
title="Links"
|
|
56
|
+
subtitle={`${links.length} link${links.length !== 1 ? 's' : ''}`}
|
|
57
|
+
theme={theme}
|
|
58
|
+
onBack={goBack}
|
|
59
|
+
elevation="subtle"
|
|
60
|
+
/>
|
|
61
|
+
|
|
62
|
+
<ScrollView style={styles.content}>
|
|
63
|
+
<View style={styles.section}>
|
|
64
|
+
<Text style={[styles.sectionTitle, { color: themeStyles.textColor }]}>Links</Text>
|
|
65
|
+
|
|
66
|
+
<GroupedSection
|
|
67
|
+
items={groupedItems}
|
|
68
|
+
theme={theme}
|
|
69
|
+
/>
|
|
70
|
+
</View>
|
|
71
|
+
</ScrollView>
|
|
72
|
+
</View>
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const styles = StyleSheet.create({
|
|
77
|
+
container: {
|
|
78
|
+
flex: 1,
|
|
79
|
+
backgroundColor: '#f2f2f2',
|
|
80
|
+
},
|
|
81
|
+
content: {
|
|
82
|
+
flex: 1,
|
|
83
|
+
padding: 16,
|
|
84
|
+
},
|
|
85
|
+
section: {
|
|
86
|
+
marginBottom: 24,
|
|
87
|
+
},
|
|
88
|
+
sectionTitle: {
|
|
89
|
+
fontSize: 16,
|
|
90
|
+
fontWeight: '600',
|
|
91
|
+
color: '#333',
|
|
92
|
+
marginBottom: 12,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export default UserLinksScreen;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { View, Text, StyleSheet, ScrollView, Platform } from 'react-native';
|
|
3
3
|
import { BaseScreenProps } from '../../navigation/types';
|
|
4
|
+
import { Header } from '../../components';
|
|
4
5
|
|
|
5
6
|
const KarmaAboutScreen: React.FC<BaseScreenProps> = ({ goBack, theme }) => {
|
|
6
7
|
const isDarkTheme = theme === 'dark';
|
|
@@ -10,7 +11,13 @@ const KarmaAboutScreen: React.FC<BaseScreenProps> = ({ goBack, theme }) => {
|
|
|
10
11
|
|
|
11
12
|
return (
|
|
12
13
|
<View style={[styles.container, { backgroundColor }]}>
|
|
13
|
-
<
|
|
14
|
+
<Header
|
|
15
|
+
title="About Karma"
|
|
16
|
+
subtitle="Learn about the karma system"
|
|
17
|
+
theme={theme}
|
|
18
|
+
onBack={goBack}
|
|
19
|
+
elevation="subtle"
|
|
20
|
+
/>
|
|
14
21
|
<ScrollView contentContainerStyle={styles.contentContainer}>
|
|
15
22
|
<Text style={[styles.paragraph, { color: textColor }]}>Karma is a recognition of your positive actions in the Oxy Ecosystem. It cannot be sent or received directly, only earned by contributing to the community.</Text>
|
|
16
23
|
<Text style={[styles.section, { color: primaryColor }]}>How to Earn Karma</Text>
|
|
@@ -37,7 +44,7 @@ const styles = StyleSheet.create({
|
|
|
37
44
|
margin: 24,
|
|
38
45
|
marginBottom: 24,
|
|
39
46
|
},
|
|
40
|
-
contentContainer: { padding: 24 },
|
|
47
|
+
contentContainer: { padding: 24, paddingTop: 20 },
|
|
41
48
|
section: { fontSize: 18, fontWeight: 'bold', marginTop: 24, marginBottom: 8 },
|
|
42
49
|
paragraph: { fontSize: 16, marginBottom: 12 },
|
|
43
50
|
});
|
|
@@ -16,7 +16,6 @@ import Avatar from '../../components/Avatar';
|
|
|
16
16
|
import { Ionicons } from '@expo/vector-icons';
|
|
17
17
|
|
|
18
18
|
const KarmaCenterScreen: React.FC<BaseScreenProps> = ({
|
|
19
|
-
onClose,
|
|
20
19
|
theme,
|
|
21
20
|
navigate,
|
|
22
21
|
goBack,
|
|
@@ -137,11 +136,6 @@ const KarmaCenterScreen: React.FC<BaseScreenProps> = ({
|
|
|
137
136
|
</View>
|
|
138
137
|
{error && <Text style={{ color: '#D32F2F', marginTop: 16, textAlign: 'center' }}>{error}</Text>}
|
|
139
138
|
</ScrollView>
|
|
140
|
-
<View style={styles.footer}>
|
|
141
|
-
<TouchableOpacity style={styles.closeButton} onPress={onClose}>
|
|
142
|
-
<Text style={[styles.closeButtonText, { color: primaryColor }]}>Close</Text>
|
|
143
|
-
</TouchableOpacity>
|
|
144
|
-
</View>
|
|
145
139
|
</View>
|
|
146
140
|
);
|
|
147
141
|
};
|
|
@@ -247,20 +241,7 @@ const styles = StyleSheet.create({
|
|
|
247
241
|
fontSize: 13,
|
|
248
242
|
marginTop: 2,
|
|
249
243
|
},
|
|
250
|
-
|
|
251
|
-
padding: 16,
|
|
252
|
-
borderTopWidth: 1,
|
|
253
|
-
borderTopColor: '#E0E0E0',
|
|
254
|
-
alignItems: 'center',
|
|
255
|
-
},
|
|
256
|
-
closeButton: {
|
|
257
|
-
paddingVertical: 8,
|
|
258
|
-
paddingHorizontal: 16,
|
|
259
|
-
},
|
|
260
|
-
closeButtonText: {
|
|
261
|
-
fontSize: 16,
|
|
262
|
-
fontWeight: '600',
|
|
263
|
-
},
|
|
244
|
+
|
|
264
245
|
message: {
|
|
265
246
|
fontSize: 16,
|
|
266
247
|
textAlign: 'center',
|