diff --git a/mobile/src/pages/Community.js b/mobile/src/pages/Community.js index cb4932fd..29ce1c93 100644 --- a/mobile/src/pages/Community.js +++ b/mobile/src/pages/Community.js @@ -1,18 +1,20 @@ import React from 'react'; -import { View, Text, FlatList, ScrollView, StyleSheet, TextInput, TouchableOpacity, Image, Alert } from 'react-native'; +import { View, Text, FlatList, ScrollView, Modal, StyleSheet, TextInput, TouchableOpacity, Image, Alert } from 'react-native'; import { useState, useEffect } from 'react'; import { useFocusEffect } from '@react-navigation/native'; import config from './config/config'; import { useAuth } from './context/AuthContext'; - const Community = ({navigation}) => { - const { userId } = useAuth(); + const { userId, accessToken } = useAuth(); const { baseURL } = config; const [posts, setPosts] = useState([]); const [users, setUsers] = useState([]); const [userMap, setUserMap] = useState([]); + const [showCommentInput, setShowCommentInput] = useState(false); + const [commentText, setCommentText] = useState(''); + const fetchPosts = async () => { const postURL = baseURL + '/posts/?page=1'; @@ -70,17 +72,48 @@ const Community = ({navigation}) => { console.error('Error:', error); } }; + + const postComment = async (comment) => { + const commentURL = `${baseURL}/comments/`; + try { + const response = await fetch(commentURL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify(comment), + }); + if (response.ok) { + const commentResponse = await response.json(); + setCommentText(''); + Alert.alert('Comment added successfully'); + } else { + console.error(response); + console.error(`Failed to post comment: ${response.status}`); + } + } catch (error) { + console.error('Error posting comment:', error); + } + }; + + useFocusEffect( - React.useCallback(() => { - fetchPosts(); - fetchUsers(); - }, []) + React.useCallback(() => { + fetchPosts(); + fetchUsers(); + }, []) ); const handleViewPost = (post) => { - navigation.navigate('Post', { postId: post.id, author: userMap[post.author] ? userMap[post.author].username : post.author }); + navigation.navigate('Post', { + postId: post.id, + author: userMap[post.author] ? userMap[post.author].username : post.author, + userMap: userMap, + post: post, + }); }; const handleCreatePost = () => { @@ -93,6 +126,29 @@ const Community = ({navigation}) => { } + const handleAddCommentButton = () => { + if(accessToken == null){ + navigation.navigate('Login&Register'); + Alert.alert('Please login to add a comment'); + return; + }else{ + setShowCommentInput(true); + + } + }; + + const handleAddComment = (postId) => { + if (commentText.trim() === '') { + return; // Ignore empty comments + } + const newComment = { + post_id: postId, + content: commentText, + }; + postComment(newComment); + setShowCommentInput(false); + }; + const renderUsername = (post) => { if(userMap[post.author]){ return userMap[post.author].username; @@ -102,7 +158,7 @@ const Community = ({navigation}) => { } const renderItem = ({ item: post }) => ( - console.log("post", post), + //console.log("post", post), {post.title} @@ -114,14 +170,12 @@ const Community = ({navigation}) => { {tag.name} ))} - {post.graph && ( - - )} + - 👍 {post.likes} + 👍 {post.liked_by.length} - + 💬 {post.comments} { renderItem={renderItem} keyExtractor={(item) => item.id.toString()} /> + + + + + + + Submit + + setShowCommentInput(false)} + > + Cancel + + + + + ); }; @@ -201,14 +278,16 @@ const styles = StyleSheet.create({ fontSize: 18, fontWeight: 'bold', marginBottom: 5, + color: 'black', }, postMeta: { - color: '#777', + color: 'black', marginBottom: 10, }, postContent: { fontSize: 16, marginBottom: 10, + color: 'black', }, tagsContainer: { flexDirection: 'row', @@ -249,6 +328,51 @@ const styles = StyleSheet.create({ color: '#fff', fontWeight: 'bold', }, + modalContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + }, + modalContent: { + width: '90%', + padding: 20, + backgroundColor: 'white', + borderRadius: 10, + }, + commentInput: { + height: 100, + borderWidth: 1, + borderColor: '#ccc', + borderRadius: 8, + padding: 10, + marginBottom: 15, + textAlignVertical: 'top', + }, + modalButtons: { + flexDirection: 'row', + justifyContent: 'space-between', + }, + submitButton: { + backgroundColor: '#28a745', + padding: 10, + borderRadius: 8, + flex: 1, + marginRight: 5, + }, + cancelButton: { + backgroundColor: '#dc3545', + padding: 10, + borderRadius: 8, + flex: 1, + marginLeft: 5, + }, + buttonText: { + color: '#ffffff', + fontSize: 16, + fontWeight: 'bold', + textAlign: 'center', + }, }); export default Community; diff --git a/mobile/src/pages/Post.js b/mobile/src/pages/Post.js index 318769c9..712a45e9 100644 --- a/mobile/src/pages/Post.js +++ b/mobile/src/pages/Post.js @@ -1,38 +1,49 @@ import React, { useState, useEffect } from 'react'; -import { ScrollView, Text, StyleSheet, View, Dimensions, TouchableOpacity } from 'react-native'; +import { + ScrollView, + Text, + StyleSheet, + View, + Dimensions, + TouchableOpacity, + TextInput, + Modal, + Alert, +} from 'react-native'; import { LineChart } from 'react-native-chart-kit'; import config from './config/config'; +import { useAuth } from './context/AuthContext'; -const Post = ({ route }) => { - const { postId, author } = route.params; +const Post = ({ navigation, route }) => { + const { postId, author, userMap, post } = route.params; const { baseURL } = config; + const { accessToken } = useAuth(); - const [post, setPost] = useState(null); const [likes, setLikes] = useState(0); const [tooltip, setTooltip] = useState(null); - //const [author, setAuthor] = useState(null); + const [showCommentInput, setShowCommentInput] = useState(false); + const [commentText, setCommentText] = useState(''); + const [comments, setComments] = useState([]); + //const [userMap, setUserMap] = useState([]); useEffect(() => { - fetchPost(); + //fetchPost(); + fetchComments(); }, [postId]); - + const fetchPost = async () => { const postURL = `${baseURL}/posts/${postId}/`; try { - const response = await fetch(postURL); + const response = await fetch(postURL, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); if (response.ok) { const postData = await response.json(); - - // Use postData directly to fetch the author setPost(postData); setLikes(postData.liked_by.length || 0); - - // Fetch author data based on postData.author - /* if (postData.author) { - fetchAuthor(postData.author); - } else { - console.warn('Post does not contain an author field.'); - } */ } else { console.error(`Failed to fetch post: ${response.status}`); } @@ -41,26 +52,89 @@ const Post = ({ route }) => { } }; + const fetchComments = async () => { + const postURL = `${baseURL}/comments/post-comments/${postId}/`; + try { + const response = await fetch(postURL, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + if (response.ok) { + console.log("response", response); + const commentsList = await response.json(); + setComments(commentsList); + + } else { + console.error(`Failed to fetch comments: ${response}`); + } + } catch (error) { + console.error('Error fetching comments:', error); + } + }; - - const fetchAuthor = async (authorId) => { - const authorURL = `${baseURL}/users/${authorId}/`; // Replace with your API's author endpoint + const postComment = async (comment) => { + const commentURL = `${baseURL}/comments/`; try { - const response = await fetch(authorURL); + const response = await fetch(commentURL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify(comment), + }); if (response.ok) { - const authorData = await response.json(); - setAuthor(authorData); + const commentResponse = await response.json(); + setComments([...comments, commentResponse]); + setCommentText(''); + Alert.alert('Comment added successfully'); } else { - console.error(`Failed to fetch author: ${response.status}`); + console.error(response); + console.error(`Failed to post comment: ${response.status}`); } } catch (error) { - console.error('Error fetching author:', error); + console.error('Error posting comment:', error); } }; + const renderUsername = (comment) => { + if(userMap[comment.user_id]){ + return userMap[comment.user_id].username; + }else{ + return comment.user_id; + } + } + + const handleLike = () => { setLikes((prevLikes) => prevLikes + 1); }; + + const handleAddCommentButton = () => { + if(accessToken == null){ + navigation.navigate('Login&Register'); + Alert.alert('Please login to add a comment'); + return; + }else{ + setShowCommentInput(true); + + } + }; + + const handleAddComment = () => { + + if (commentText.trim() === '') { + return; // Ignore empty comments + } + const newComment = { + post_id: postId, + content: commentText, + }; + postComment(newComment); + setShowCommentInput(false); + }; const stockData = [142, 145, 143, 141, 144, 140, 138, 139]; @@ -69,6 +143,9 @@ const Post = ({ route }) => { } return ( + //console.log("comment", comments), + //console.log("userMap", userMap), + //console.log(accessToken), {post.title} Author: {author ? author : 'Unknown'} @@ -79,7 +156,7 @@ const Post = ({ route }) => { {post.tags.length > 0 ? ( post.tags.map((tag) => ( - #{tag.name} + {tag.name} )) ) : ( @@ -87,68 +164,121 @@ const Post = ({ route }) => { )} {post.content} + Stock Price Chart + + `rgba(0, 123, 255, ${opacity})`, // Blue line color + labelColor: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`, // Black label color + style: { + borderRadius: 16, + }, + propsForDots: { + r: '6', + strokeWidth: '2', + + }, + }} + bezier + style={styles.chart} + onDataPointClick={({ value, x, y, index }) => { + setTooltip({ + value: `${value}`, + x, + y, + index, + }); + }} + /> + + {/* Tooltip for the clicked point */} + {tooltip && ( + + {tooltip.value} + + )} + - - {/* Buttons */} 👍 Like ({likes}) - + 💬 Add Comment - {/* Chart Section */} - Stock Price Chart - `rgba(26, 255, 146, ${opacity})`, - labelColor: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`, - style: { - borderRadius: 16, - }, - propsForDots: { - r: '6', - strokeWidth: '2', - stroke: '#ffa726', - }, - }} - bezier - style={styles.chart} - onDataPointClick={({ value, x, y }) => setTooltip({ value, x, y })} - /> - {tooltip && ( - - ${tooltip.value} + + + Comments + {comments.length > 0 ? ( + comments.map((comment) => ( + + {renderUsername(comment)} + {comment.content} + + + )) + ) : ( + No comments yet. + )} + + + + + + + + + + Submit + + setShowCommentInput(false)} + > + Cancel + + + - )} + ); }; - - const styles = StyleSheet.create({ container: { flex: 1, @@ -218,6 +348,88 @@ const styles = StyleSheet.create({ fontSize: 14, color: '#aaa', }, + comments: { + marginBottom: 20, + }, + commentsTitle: { + fontSize: 20, + fontWeight: 'bold', + marginTop: 20, + }, + commentContainer: { + marginBottom: 15, + padding: 10, + backgroundColor: '#f9f9f9', + borderRadius: 10, + }, + commentAuthor: { + fontWeight: 'bold', + marginBottom: 5, + }, + commentText: { + marginBottom: 5, + }, + commentDate: { + fontSize: 12, + color: '#999', + }, + modalContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + }, + modalContent: { + width: '90%', + padding: 20, + backgroundColor: 'white', + borderRadius: 10, + }, + commentInput: { + height: 100, + borderWidth: 1, + borderColor: '#ccc', + borderRadius: 8, + padding: 10, + marginBottom: 15, + textAlignVertical: 'top', + }, + modalButtons: { + flexDirection: 'row', + justifyContent: 'space-between', + }, + submitButton: { + backgroundColor: '#28a745', + padding: 10, + borderRadius: 8, + flex: 1, + marginRight: 5, + }, + cancelButton: { + backgroundColor: '#dc3545', + padding: 10, + borderRadius: 8, + flex: 1, + marginLeft: 5, + }, + noComments: { + fontSize: 14, + color: '#aaa', + marginTop: 10, + }, + tooltip: { + position: 'absolute', + backgroundColor: 'rgba(0, 0, 0, 0.8)', // Dark background for contrast + padding: 5, + borderRadius: 5, + alignItems: 'center', + justifyContent: 'center', + }, + tooltipText: { + color: '#ffffff', // White text for visibility + fontSize: 12, + fontWeight: 'bold', + }, }); export default Post;