From c9e7eecddf11b51a9fe0944270553036835bbbdd Mon Sep 17 00:00:00 2001 From: Mallepally Lokeshwar Reddy Date: Thu, 22 Aug 2024 19:42:22 +0530 Subject: [PATCH] Implement like/dislike system for comments and tutorials --- src/components/Card/CardWithPicture.jsx | 44 +------ src/components/Card/CardWithoutPicture.jsx | 46 +------ .../components/Commnets/Comment.jsx | 45 +------ .../TutorialPage/components/PostDetails.jsx | 45 +------ src/components/TutorialPage/index.jsx | 3 +- .../ui-helpers/CommentLikesDislikes.jsx | 121 ++++++++++++++++++ .../ui-helpers/TutorialLikesDislikes.jsx | 121 ++++++++++++++++++ 7 files changed, 253 insertions(+), 172 deletions(-) create mode 100644 src/components/ui-helpers/CommentLikesDislikes.jsx create mode 100644 src/components/ui-helpers/TutorialLikesDislikes.jsx diff --git a/src/components/Card/CardWithPicture.jsx b/src/components/Card/CardWithPicture.jsx index e8cd65c2..bb0c37a7 100644 --- a/src/components/Card/CardWithPicture.jsx +++ b/src/components/Card/CardWithPicture.jsx @@ -15,13 +15,11 @@ import ShareOutlinedIcon from "@mui/icons-material/ShareOutlined"; import ChatOutlinedIcon from "@mui/icons-material/ChatOutlined"; import TurnedInNotOutlinedIcon from "@mui/icons-material/TurnedInNotOutlined"; import MoreVertOutlinedIcon from "@mui/icons-material/MoreVertOutlined"; -import { ToggleButton, ToggleButtonGroup } from "@mui/material"; -import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; -import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import { Link } from "react-router-dom"; import { useDispatch, useSelector } from "react-redux"; import { useFirebase, useFirestore } from "react-redux-firebase"; import { getUserProfileData } from "../../store/actions"; +import TutorialLikesDislikes from "../ui-helpers/TutorialLikesDislikes"; const useStyles = makeStyles(theme => ({ root: { @@ -73,22 +71,9 @@ const useStyles = makeStyles(theme => ({ export default function CardWithPicture({ tutorial }) { const classes = useStyles(); - const [alignment, setAlignment] = React.useState("left"); - const [count, setCount] = useState(1); const dispatch = useDispatch(); const firebase = useFirebase(); const firestore = useFirestore(); - const handleIncrement = () => { - setCount(count + 1); - }; - - const handleDecrement = () => { - setCount(count - 1); - }; - - const handleAlignment = (event, newAlignment) => { - setAlignment(newAlignment); - }; useEffect(() => { getUserProfileData(tutorial?.created_by)(firebase, firestore, dispatch); @@ -189,32 +174,7 @@ export default function CardWithPicture({ tutorial }) { {"10 min"}
- - - - {count} - - - - - + diff --git a/src/components/Card/CardWithoutPicture.jsx b/src/components/Card/CardWithoutPicture.jsx index abc4ecb0..b5caed3b 100644 --- a/src/components/Card/CardWithoutPicture.jsx +++ b/src/components/Card/CardWithoutPicture.jsx @@ -14,13 +14,11 @@ import ShareOutlinedIcon from "@mui/icons-material/ShareOutlined"; import ChatOutlinedIcon from "@mui/icons-material/ChatOutlined"; import TurnedInNotOutlinedIcon from "@mui/icons-material/TurnedInNotOutlined"; import MoreVertOutlinedIcon from "@mui/icons-material/MoreVertOutlined"; -import ToggleButton from "@mui/lab/ToggleButton"; -import ToggleButtonGroup from "@mui/lab/ToggleButtonGroup"; -import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; -import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import { useDispatch, useSelector } from "react-redux"; import { useFirebase, useFirestore } from "react-redux-firebase"; import { getUserProfileData } from "../../store/actions"; +import TutorialLikesDislikes from "../ui-helpers/TutorialLikesDislikes"; + const useStyles = makeStyles(theme => ({ root: { margin: "0.5rem", @@ -67,22 +65,9 @@ const useStyles = makeStyles(theme => ({ export default function CardWithoutPicture({ tutorial }) { const classes = useStyles(); - const [alignment, setAlignment] = React.useState("left"); - const [count, setCount] = useState(1); const dispatch = useDispatch(); const firebase = useFirebase(); const firestore = useFirestore(); - const handleIncrement = () => { - setCount(count + 1); - }; - - const handleDecrement = () => { - setCount(count - 1); - }; - - const handleAlignment = (event, newAlignment) => { - setAlignment(newAlignment); - }; useEffect(() => { getUserProfileData(tutorial?.created_by)(firebase, firestore, dispatch); @@ -178,32 +163,7 @@ export default function CardWithoutPicture({ tutorial }) { {"10 min"}
- - - - {count} - - - - - + diff --git a/src/components/TutorialPage/components/Commnets/Comment.jsx b/src/components/TutorialPage/components/Commnets/Comment.jsx index 09e486fc..e73c44d3 100644 --- a/src/components/TutorialPage/components/Commnets/Comment.jsx +++ b/src/components/TutorialPage/components/Commnets/Comment.jsx @@ -9,10 +9,6 @@ import { import { makeStyles } from "@mui/styles"; import CardActions from "@mui/material/CardActions"; import MoreVertOutlinedIcon from "@mui/icons-material/MoreVertOutlined"; -import ToggleButton from "@mui/lab/ToggleButton"; -import ToggleButtonGroup from "@mui/lab/ToggleButtonGroup"; -import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; -import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import React, { useState, useEffect, @@ -29,6 +25,7 @@ import { getCommentReply, addComment } from "../../../../store/actions/tutorialPageActions"; +import CommentLikesDislikes from "../../../ui-helpers/CommentLikesDislikes"; const useStyles = makeStyles(() => ({ container: { margin: "10px 0", @@ -55,7 +52,6 @@ const useStyles = makeStyles(() => ({ const Comment = ({ id }) => { const classes = useStyles(); const [showReplyfield, setShowReplyfield] = useState(false); - const [alignment, setAlignment] = React.useState("left"); const [count, setCount] = useState(1); const firestore = useFirestore(); const firebase = useFirebase(); @@ -84,18 +80,6 @@ const Comment = ({ id }) => { const [replies] = repliesArray.filter(replies => replies.comment_id == id); - const handleIncrement = () => { - setCount(count + 1); - }; - - const handleDecrement = () => { - setCount(count - 1); - }; - - const handleAlignment = (event, newAlignment) => { - setAlignment(newAlignment); - }; - const handleSubmit = comment => { const commentData = { content: comment, @@ -129,32 +113,7 @@ const Comment = ({ id }) => { Reply )} - - - - {count} - - - - - + diff --git a/src/components/TutorialPage/components/PostDetails.jsx b/src/components/TutorialPage/components/PostDetails.jsx index 1c46ac63..5ce05bdc 100644 --- a/src/components/TutorialPage/components/PostDetails.jsx +++ b/src/components/TutorialPage/components/PostDetails.jsx @@ -8,16 +8,13 @@ import ShareOutlinedIcon from "@mui/icons-material/ShareOutlined"; import ChatOutlinedIcon from "@mui/icons-material/ChatOutlined"; import TurnedInNotOutlinedIcon from "@mui/icons-material/TurnedInNotOutlined"; import MoreVertOutlinedIcon from "@mui/icons-material/MoreVertOutlined"; -import ToggleButton from "@mui/lab/ToggleButton"; -import ToggleButtonGroup from "@mui/lab/ToggleButtonGroup"; -import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; -import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import User from "./UserDetails"; import { useDispatch, useSelector } from "react-redux"; import { useFirebase, useFirestore } from "react-redux-firebase"; import { getUserProfileData } from "../../../store/actions"; import { HashLink } from "react-router-hash-link"; import { useParams } from "react-router-dom"; +import TutorialLikesDislikes from "../../ui-helpers/TutorialLikesDislikes"; const useStyles = makeStyles(() => ({ container: { padding: "20px", @@ -45,8 +42,6 @@ const PostDetails = ({ details }) => { const dispatch = useDispatch(); const firebase = useFirebase(); const firestore = useFirestore(); - const [alignment, setAlignment] = React.useState("left"); - const [count, setCount] = useState(details.upVote - details.downVote || 0); const { id } = useParams(); useEffect(() => { @@ -65,17 +60,6 @@ const PostDetails = ({ details }) => { return timestamp.toDate().toDateString(); }; - const handleIncrement = () => { - setCount(count + 1); - }; - - const handleDecrement = () => { - setCount(count - 1); - }; - - const handleAlignment = (event, newAlignment) => { - setAlignment(newAlignment); - }; const classes = useStyles(); return ( <> @@ -108,32 +92,7 @@ const PostDetails = ({ details }) => { - - - - {count} - - - - - + diff --git a/src/components/TutorialPage/index.jsx b/src/components/TutorialPage/index.jsx index dd7895c2..ce0d6516 100644 --- a/src/components/TutorialPage/index.jsx +++ b/src/components/TutorialPage/index.jsx @@ -57,7 +57,8 @@ function TutorialPage({ background = "white", textColor = "black" }) { upVote: tutorial?.upVotes, downVote: tutorial?.downVotes, published_on: tutorial?.createdAt, - tag: tutorial?.tut_tags + tag: tutorial?.tut_tags, + tutorial_id: tutorial?.tutorial_id }; const steps = useSelector( diff --git a/src/components/ui-helpers/CommentLikesDislikes.jsx b/src/components/ui-helpers/CommentLikesDislikes.jsx new file mode 100644 index 00000000..916a7659 --- /dev/null +++ b/src/components/ui-helpers/CommentLikesDislikes.jsx @@ -0,0 +1,121 @@ +import React, { useState, useEffect } from "react"; +import { ToggleButton, ToggleButtonGroup, Typography } from "@mui/material"; +import { ThumbUp, ThumbDown } from "@mui/icons-material"; +import firebase from "../../config/index"; + +const CommentLikesDislikes = ({ comment_id }) => { + const [userChoice, setUserChoice] = useState(null); + const [upVotes, setUpVotes] = useState(0); + const [downVotes, setDownVotes] = useState(0); + const db = firebase.firestore(); + + useEffect(() => { + const userId = firebase.auth().currentUser?.uid; + + if (!userId) { + // User not authenticated + return; + } + + const commentDocRef = db.collection("cl_comments").doc(comment_id); + const userChoiceRef = db + .collection("comment_likes") + .doc(`${comment_id}_${userId}`); + + // Fetch initial data and set up real-time listeners + const fetchData = async () => { + try { + const commentDoc = await commentDocRef.get(); + if (!commentDoc.exists) { + throw new Error("Comment not found"); + } + + // Fetch existing choice of user (if any) + const userChoiceDoc = await userChoiceRef.get(); + if (userChoiceDoc.exists) { + const existingChoice = userChoiceDoc.data().value; + setUserChoice(existingChoice === 1 ? "like" : "dislike"); + } else { + setUserChoice(null); + } + + // Subscribe to real-time updates for likes and dislikes + const unsubscribeLikes = db + .collection("comment_likes") + .where("comment_id", "==", comment_id) + .where("value", "==", 1) + .onSnapshot(snapshot => { + setUpVotes(snapshot.size); + commentDocRef.update({ upVotes: snapshot.size }); + }); + + const unsubscribeDislikes = db + .collection("comment_likes") + .where("comment_id", "==", comment_id) + .where("value", "==", -1) + .onSnapshot(snapshot => { + setDownVotes(snapshot.size); + commentDocRef.update({ downVotes: snapshot.size }); + }); + + // Cleanup function to unsubscribe from listeners when component unmounts + return () => { + unsubscribeLikes(); + unsubscribeDislikes(); + }; + } catch (error) { + console.error("Error fetching comment data:", error); + } + }; + + fetchData(); + }, [comment_id, db]); + + const handleUserChoice = async (event, newChoice) => { + if (userChoice === newChoice) return; + + const userId = firebase.auth().currentUser?.uid; + if (!userId) return; + + try { + const userChoiceRef = db + .collection("comment_likes") + .doc(`${comment_id}_${userId}`); + + if (newChoice) { + const value = newChoice === "like" ? 1 : -1; + await userChoiceRef.set( + { uid: userId, comment_id, value }, + { merge: true } + ); + } else { + await userChoiceRef.delete(); + } + + setUserChoice(newChoice); + } catch (error) { + console.error("Error setting user choice:", error); + } + }; + + return ( + + + + {upVotes} + + + + {downVotes} + + + ); +}; + +export default CommentLikesDislikes; diff --git a/src/components/ui-helpers/TutorialLikesDislikes.jsx b/src/components/ui-helpers/TutorialLikesDislikes.jsx new file mode 100644 index 00000000..5c016342 --- /dev/null +++ b/src/components/ui-helpers/TutorialLikesDislikes.jsx @@ -0,0 +1,121 @@ +import React, { useState, useEffect } from "react"; +import { ToggleButton, ToggleButtonGroup, Typography } from "@mui/material"; +import { ThumbUp, ThumbDown } from "@mui/icons-material"; +import firebase from "../../config/index"; + +const TutorialLikesDislikes = ({ tutorial_id }) => { + const [userChoice, setUserChoice] = useState(null); + const [upVotes, setUpVotes] = useState(0); + const [downVotes, setDownVotes] = useState(0); + const db = firebase.firestore(); + + useEffect(() => { + const userId = firebase.auth().currentUser?.uid; + + if (!userId) { + // User not authenticated + return; + } + + const tutorialDocRef = db.collection("tutorials").doc(tutorial_id); + const userChoiceRef = db + .collection("tutorial_likes") + .doc(`${tutorial_id}_${userId}`); + + // Fetch initial data and set up real-time listeners + const fetchData = async () => { + try { + const tutorialDoc = await tutorialDocRef.get(); + if (!tutorialDoc.exists) { + throw new Error("Tutorial not found"); + } + + // Fetch existing choice of user (if any) + const userChoiceDoc = await userChoiceRef.get(); + if (userChoiceDoc.exists) { + const existingChoice = userChoiceDoc.data().value; + setUserChoice(existingChoice === 1 ? "like" : "dislike"); + } else { + setUserChoice(null); + } + + // Subscribe to real-time updates for likes and dislikes + const unsubscribeLikes = db + .collection("tutorial_likes") + .where("tut_id", "==", tutorial_id) + .where("value", "==", 1) + .onSnapshot(snapshot => { + setUpVotes(snapshot.size); + tutorialDocRef.update({ upVotes: snapshot.size }); + }); + + const unsubscribeDislikes = db + .collection("tutorial_likes") + .where("tut_id", "==", tutorial_id) + .where("value", "==", -1) + .onSnapshot(snapshot => { + setDownVotes(snapshot.size); + tutorialDocRef.update({ downVotes: snapshot.size }); + }); + + // Cleanup function to unsubscribe from listeners when component unmounts + return () => { + unsubscribeLikes(); + unsubscribeDislikes(); + }; + } catch (error) { + console.error("Error fetching tutorial data:", error); + } + }; + + fetchData(); + }, [tutorial_id, db]); + + const handleUserChoice = async (event, newChoice) => { + if (userChoice === newChoice) return; + + const userId = firebase.auth().currentUser?.uid; + if (!userId) return; + + try { + const userChoiceRef = db + .collection("tutorial_likes") + .doc(`${tutorial_id}_${userId}`); + + if (newChoice) { + const value = newChoice === "like" ? 1 : -1; + await userChoiceRef.set( + { uid: userId, tut_id: tutorial_id, value }, + { merge: true } + ); + } else { + await userChoiceRef.delete(); + } + + setUserChoice(newChoice); + } catch (error) { + console.error("Error setting user choice:", error); + } + }; + + return ( + + + + {upVotes} + + + + {downVotes} + + + ); +}; + +export default TutorialLikesDislikes;