diff --git a/3Design/mobile/app/navigation/StackNavigator.js b/3Design/mobile/app/navigation/StackNavigator.js
index e39dc863..151d6fd0 100644
--- a/3Design/mobile/app/navigation/StackNavigator.js
+++ b/3Design/mobile/app/navigation/StackNavigator.js
@@ -5,6 +5,7 @@ import RegisterScreen from '../screens/RegisterScreen';
import TabNavigator from './TabNavigator';
import PostScreen from "../screens/PostScreen";
import LeaderboardScreen from '../screens/LeaderboardScreen';
+import ProfilePage from '../screens/ProfileScreen'; // Import ProfilePage
const Stack = createNativeStackNavigator();
@@ -42,6 +43,12 @@ export default function StackNavigator() {
component={LeaderboardScreen}
options={{ title: 'Leaderboard' }}
/>
+ {/* Add the ProfilePage route */}
+
);
}
diff --git a/3Design/mobile/app/screens/LeaderboardScreen.js b/3Design/mobile/app/screens/LeaderboardScreen.js
index 38a3de1b..631c4da7 100644
--- a/3Design/mobile/app/screens/LeaderboardScreen.js
+++ b/3Design/mobile/app/screens/LeaderboardScreen.js
@@ -7,11 +7,12 @@ import {
ActivityIndicator,
Platform,
StatusBar,
+ Image,
+ TouchableOpacity
} from 'react-native';
import axios from 'axios';
import { Colors } from '../constants/Colors';
import { AuthContext } from "../context/AuthContext";
-import { Categories } from "../constants/Categories";
export default function LeaderboardScreen({ route, navigation }) {
const { category } = route.params;
@@ -41,12 +42,46 @@ export default function LeaderboardScreen({ route, navigation }) {
}
}, [category]);
+ const handleViewPost = (item) => {
+ const postUrl = `${process.env.EXPO_PUBLIC_VITE_API_URL}/api/v1/posts/${item.postId}`;
+ axios
+ .get(postUrl, {
+ headers: { Authorization: `Bearer ${user.accessToken}` },
+ })
+ .then((response) => {
+ const postData = response.data;
+ navigation.navigate('Post', {
+ postId: item.postId,
+ username: item.user.nickName,
+ userId: item.user.id,
+ title: postData.title,
+ content: postData.text,
+ model: postData.fileUrl,
+ });
+ })
+ .catch((error) => console.error(error));
+ };
+
const renderItem = ({ item, index }) => (
{index + 1}
- {item.user.nickName}
+ navigation.navigate('ProfilePage', { userId: item.user.id })}
+ >
+
+ {item.user.nickName}
+
{item.score}
- {item.postId}
+ handleViewPost(item)}
+ >
+ View Post
+
);
@@ -127,6 +162,22 @@ const styles = StyleSheet.create({
textAlign: 'center',
fontSize: 16,
},
+ userCell: {
+ flex: 2,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ profileImage: {
+ width: 30,
+ height: 30,
+ borderRadius: 15,
+ marginRight: 8,
+ },
+ userName: {
+ fontSize: 16,
+ textAlign: 'center',
+ },
loaderContainer: {
flex: 1,
justifyContent: 'center',
@@ -141,4 +192,16 @@ const styles = StyleSheet.create({
fontSize: 18,
fontWeight: '500',
},
+ viewPostButton: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ paddingVertical: 8,
+ },
+ viewPostButtonText: {
+ color: Colors.dark,
+ fontSize: 16,
+ fontWeight: '600',
+ textDecorationLine: 'underline',
+ },
});
diff --git a/3Design/mobile/app/screens/ProfileScreen.js b/3Design/mobile/app/screens/ProfileScreen.js
index df53e3cd..02b33481 100644
--- a/3Design/mobile/app/screens/ProfileScreen.js
+++ b/3Design/mobile/app/screens/ProfileScreen.js
@@ -7,7 +7,7 @@ import {
FlatList,
TouchableOpacity,
Modal,
- Alert,
+ ActivityIndicator
} from 'react-native';
import Post from '../components/Post';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
@@ -19,17 +19,24 @@ import { MaterialIcons } from '@expo/vector-icons';
const ProfilePage = () => {
const route = useRoute();
+ const navigation = useNavigation();
+ const { user } = useContext(AuthContext);
+
+ // Use the userId from params if provided; else fallback to currently logged-in user's userId
+ const userId = route.params?.userId || user.userId;
const [isAchievementsVisible, setAchievementsVisible] = useState(false);
const [achievements, setAchievements] = useState([]);
-
- const [userData, setUserData] = useState({});
+ const [userData, setUserData] = useState(null);
const [latestPosts, setLatestPosts] = useState([]);
const [filteredPosts, setFilteredPosts] = useState([]);
+
const [loadingPosts, setLoadingPosts] = useState(true);
+ const [loadingUserData, setLoadingUserData] = useState(true);
+ const [loadingAchievements, setLoadingAchievements] = useState(true);
+
const [showVisual, setShowVisual] = useState(false);
- const { user } = useContext(AuthContext);
- const userId = route.params?.userId || user.userId;
+ const flatListRef = useRef(null);
const fetchUserPosts = async () => {
try {
@@ -41,17 +48,15 @@ const ProfilePage = () => {
);
setLatestPosts(response.data);
filterPosts(response.data, true);
- setLoadingPosts(false);
} catch (error) {
- // console.error('Error fetching posts:', error);
+ console.error('Error fetching posts:', error);
+ } finally {
setLoadingPosts(false);
- // Alert.alert('Error', 'Failed to fetch posts');
}
};
const fetchUserData = async () => {
try {
- // Fetch the user profile data by username
const response = await axios.get(
`${process.env.EXPO_PUBLIC_VITE_API_URL}/api/v1/users/${userId}`,
{
@@ -61,6 +66,8 @@ const ProfilePage = () => {
setUserData(response.data);
} catch (error) {
console.error('Error fetching user data:', error);
+ } finally {
+ setLoadingUserData(false);
}
};
@@ -75,10 +82,13 @@ const ProfilePage = () => {
setAchievements(response.data);
} catch (error) {
console.error('Error fetching achievements:', error);
+ } finally {
+ setLoadingAchievements(false);
}
};
const filterPosts = (allPosts, showVisual) => {
+ // Currently not filtering by showVisual, just display all
setFilteredPosts(allPosts);
};
@@ -88,7 +98,6 @@ const ProfilePage = () => {
const clearFilteredPosts = () => {
setFilteredPosts([]);
- console.log('Filtered posts cleared.');
};
useEffect(() => {
@@ -97,32 +106,48 @@ const ProfilePage = () => {
fetchAchievements();
}, [user, userId]);
- const navigation = useNavigation();
-
- const flatListRef = useRef(null);
-
const disableScroll = (isDisabled) => {
if (flatListRef.current) {
flatListRef.current.setNativeProps({ scrollEnabled: !isDisabled });
}
};
+ // If we're still loading user data, show a loader
+ if (loadingUserData || loadingPosts || loadingAchievements) {
+ return (
+
+
+
+ );
+ }
+
+ // If userData is null even after loading, show a fallback UI
+ if (!userData) {
+ return (
+
+ Failed to load user profile.
+
+ );
+ }
+
return (
- {/* Display Username at the top of the page */}
- {userData.nickName}
+ {/* User's Nickname (or fallback if unavailable) */}
+
+ {userData?.nickName || 'Unknown User'}
+
- {/* Profile Picture */}
+ {/* Profile Picture (with fallback) */}
- {/* Tournament Points */}
+ {/* Tournament Points (with fallback) */}
- Tournament Points: {userData.experience}
+ Tournament Points: {userData?.experience ?? 0}
{
{/* Latest Posts */}
Latest Posts:
-
+
item.postId.toString()}
- removeClippedSubviews={false}
keyboardShouldPersistTaps='handled'
- ListEmptyComponent={This user hasn't posted yet.}
+ ListEmptyComponent={
+ This user hasn't posted yet.
+ }
renderItem={({ item }) => (
{
)}
/>
+
{
Achievements
-
{/* Table Header */}
Title
@@ -189,9 +215,11 @@ const ProfilePage = () => {
{item.description}
{item.point}
- {new Intl.DateTimeFormat('en-GB').format(
- new Date(item.earnedAt)
- )}
+ {item.earnedAt
+ ? new Intl.DateTimeFormat('en-GB').format(
+ new Date(item.earnedAt)
+ )
+ : ''}
)}
@@ -216,6 +244,17 @@ const ProfilePage = () => {
};
const styles = StyleSheet.create({
+ loaderContainer: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ },
+ errorText: {
+ color: Colors.red,
+ fontSize: 18,
+ textAlign: 'center',
+ marginTop: 20,
+ },
container: {
flex: 1,
padding: 20,
@@ -245,20 +284,10 @@ const styles = StyleSheet.create({
fontWeight: 'bold',
marginBottom: 10,
},
- itemContainer: {
- marginBottom: 10,
- padding: 10,
- borderWidth: 1,
- borderColor: '#ccc',
- borderRadius: 8,
- },
- itemType: {
- fontSize: 14,
- fontWeight: 'bold',
- marginBottom: 4,
- },
- itemTitle: {
- fontSize: 16,
+ emptyText: {
+ textAlign: 'center',
+ marginTop: 20,
+ fontStyle: 'italic',
},
achievementsButton: {
backgroundColor: Colors.dark,
@@ -284,17 +313,13 @@ const styles = StyleSheet.create({
flex: 1,
justifyContent: 'center',
alignItems: 'center',
- backgroundColor: 'rgba(0, 0, 0, 0.5)', // Semi-transparent background
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
centeredModal: {
- width: '95%', // Adjust width as needed
+ width: '95%',
backgroundColor: '#fff',
borderRadius: 10,
padding: 20,
- shadowColor: '#000',
- shadowOffset: { width: 0, height: 2 },
- shadowOpacity: 0.25,
- shadowRadius: 4,
elevation: 5,
},
modalTitle: {
@@ -310,7 +335,6 @@ const styles = StyleSheet.create({
paddingVertical: 10,
borderRadius: 5,
marginBottom: 5,
- height: '60',
},
headerCell: {
fontSize: 15,