From 94a7451f72523fbf8249e21bb3116ef95f895d1d Mon Sep 17 00:00:00 2001 From: balaharisankar Date: Tue, 6 Aug 2024 15:10:07 +0530 Subject: [PATCH] Upvote/downvote functionality added in frontend --- .../src/pages/Q&A/AnswerModel/AnswerModel.jsx | 117 +++++++++++------- .../pages/Q&A/AnswerModel/AnswerModel.scss | 20 ++- frontend/src/pages/Q&A/Q&A.jsx | 35 +++++- frontend/src/pages/Q&A/Ques.scss | 5 + frontend/src/service/Faq.jsx | 48 ++++++- 5 files changed, 175 insertions(+), 50 deletions(-) diff --git a/frontend/src/pages/Q&A/AnswerModel/AnswerModel.jsx b/frontend/src/pages/Q&A/AnswerModel/AnswerModel.jsx index aaf075af..f7d087e4 100644 --- a/frontend/src/pages/Q&A/AnswerModel/AnswerModel.jsx +++ b/frontend/src/pages/Q&A/AnswerModel/AnswerModel.jsx @@ -1,31 +1,34 @@ import React, { useEffect, useState } from "react"; import { Modal, Backdrop, Fade } from '@material-ui/core'; import { SimpleToast } from '../../../components/util/Toast' -import {postAnswer,getAnswers} from '../../../service/Faq' +import { postAnswer, getAnswers,upvoteAnswer,downvoteAnswer } from '../../../service/Faq' import style from './AnswerModel.scss' export function AnswerModel(props) { + let dark=props.theme const [answer, setAnswer] = useState("") - const[answers,setAnswers]=useState([]) + const [author, setAuthor] = useState("") + const [answers, setAnswers] = useState([]) const [toast, setToast] = useState({ toastStatus: false, toastType: "", toastMessage: "", }); - const filterAnswers=(fetchedAnswers)=>{ - return fetchedAnswers.filter((ans)=>{return ans.isApproved==true}) + const filterAnswers = (fetchedAnswers) => { + return fetchedAnswers.filter((ans) => { return ans.isApproved == true }) } - async function fetchAnswers(){ - const data=await getAnswers(props.data._id,setToast) + async function fetchAnswers() { + const data = await getAnswers(props.data._id, setToast) setAnswers(filterAnswers(data)) } - useEffect(()=>{ - fetchAnswers() - },[props]) - function timeStampFormatter(time){ - const months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"] - const messageTime=new Date(time) - return `${String(messageTime.getDate())} ${String(months[messageTime.getMonth()])} ${String(messageTime.getFullYear())} ${String(messageTime.getHours()%12 || 12).padStart(2,'0')}:${String(messageTime.getMinutes()).padStart(2,'0')} ${messageTime.getHours()>=12?'pm':'am'}` + useEffect(() => { + if (props.open) + fetchAnswers() + }, [props]) + function timeStampFormatter(time) { + const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] + const messageTime = new Date(time) + return `${String(messageTime.getDate())} ${String(months[messageTime.getMonth()])} ${String(messageTime.getFullYear())} ${String(messageTime.getHours() % 12 || 12).padStart(2, '0')}:${String(messageTime.getMinutes()).padStart(2, '0')} ${messageTime.getHours() >= 12 ? 'pm' : 'am'}` } const Tags = [ { value: "ml" }, @@ -43,22 +46,31 @@ export function AnswerModel(props) { ]; function handleSubmit(e) { e.preventDefault() - if(answer!=""){ - let data={question_id:props.data._id,answer,created_on:new Date(),created_by:"Anonymous"} - postAnswer(data,setToast) + if (answer != "" && author != "") { + let data = { question_id: props.data._id, answer, created_on: new Date(), created_by: author } + postAnswer(data, setToast) setAnswer("") + setAuthor("") props.handleClose(false) - }else{ - setToast({toastStatus:true,toastMessage:"Please enter your answer",toastType:"error"}) + } else { + setToast({ toastStatus: true, toastMessage: "Please fill both the fields", toastType: "error" }) } } + const handleUpvote=async(answerId)=>{ + await upvoteAnswer(answerId,setToast) + fetchAnswers() + } + const handleDownvote=async(answerId)=>{ + await downvoteAnswer(answerId,setToast) + fetchAnswers() + } return (
{toast.toastStatus && ( {setToast({toastMessage:"",toastStatus:false,toastType:""})}} + handleCloseToast={() => { setToast({ toastMessage: "", toastStatus: false, toastType: "" }) }} severity={toast.toastType} /> )} @@ -75,18 +87,18 @@ export function AnswerModel(props) { }} > -
+
- { + { setAnswer("") props.handleClose(false) - }}> - + }}> +
-

{props.data?.title}

-

{props.data?.description}

-
+

{props.data?.title}

+

{props.data?.description}

+
{ props && props.data?.tags?.map((tag, index) => { if (tag) @@ -97,29 +109,44 @@ export function AnswerModel(props) { }
+ { setAuthor(e.target.value) }} value={author} type="text" placeholder="Your Name" /> { setAnswer(e.target.value) }} value={answer} type="text" placeholder="Post your answer" /> - +
-

Answers ({answers.length})

+

Answers ({answers.length})

{ - answers.length==0? -

No answers found...

- : -
- { - answers.map((ans,index)=>{ - return( -
-
-
{ans.created_by}
-

{timeStampFormatter(ans.created_on)}

+ answers.length == 0 ? +

No answers found...

+ : +
+ { + answers.map((ans, index) => { + return ( +
+
+
{ans.created_by || "Anonymous"}
+

{timeStampFormatter(ans.created_on)}

+
+

{ans.answer}

+
+ + +
-

{ans.answer}

-
- ) - }) - } -
+ ) + }) + } +
}
diff --git a/frontend/src/pages/Q&A/AnswerModel/AnswerModel.scss b/frontend/src/pages/Q&A/AnswerModel/AnswerModel.scss index 3524d2a8..0f486d26 100644 --- a/frontend/src/pages/Q&A/AnswerModel/AnswerModel.scss +++ b/frontend/src/pages/Q&A/AnswerModel/AnswerModel.scss @@ -94,6 +94,7 @@ p { margin-top: 8px; + margin-bottom: 8px; font-size: 16px; } } @@ -112,6 +113,17 @@ margin: 0; } } +.vote-btn { + background-color: #69a9dd; + outline: 1px solid white; + color: white; + border: none; + border-radius: 5px; + padding: 5px; + margin: 5px; + cursor: pointer; + } + @media screen and (max-width:768px) { .modal-container { @@ -127,6 +139,12 @@ } .post-answer-btn { - width: 25%; + width: 100%; + } + .answer-form{ + flex-direction: column; + } + .answer-field{ + width: 100%; } } \ No newline at end of file diff --git a/frontend/src/pages/Q&A/Q&A.jsx b/frontend/src/pages/Q&A/Q&A.jsx index 26698e49..6b6cae33 100644 --- a/frontend/src/pages/Q&A/Q&A.jsx +++ b/frontend/src/pages/Q&A/Q&A.jsx @@ -50,6 +50,7 @@ function Ques(props) { title: "", description: "", tags: [], + created_by:"" }); const handleCloseToast = () => { @@ -79,6 +80,7 @@ function Ques(props) { title: Joi.string().required(), body: Joi.string().required(), tags: Joi.required(), + created_by:Joi.string().required() }; const validate = () => { @@ -188,6 +190,9 @@ function Ques(props) { ))}
+
+

- {item?.created_by||"Anonymous"}

+
@@ -214,7 +219,7 @@ function Ques(props) { }}>Answers
) - )}; + )}
} {toast.toastStatus && ( @@ -251,6 +256,34 @@ function Ques(props) {
+
+
+ + +
+ {formerrors["title"] ? ( +
* {formerrors["title"]}
+ ) : ( +
   
+ )} +
+
+
{ export const postAnswer = async (data, setToast) => { try { - showToast(setToast,"Posting...","info") + showToast(setToast, "Posting...", "info") const url = `${END_POINT}/answers/`; const response = await fetch(url, { method: "POST", @@ -339,8 +339,8 @@ export const postAnswer = async (data, setToast) => { body: JSON.stringify(data), }); const res = await response.json(); - if(response.status==200) - showToast(setToast, "Thanks for answering, it has been sent to admins for review and will appear here on approval","success"); + if (response.status == 200) + showToast(setToast, "Thanks for answering, it has been sent to admins for review and will appear here on approval", "success"); else showToast(setToast, "Failed to Post Answer", "error"); return res; @@ -349,3 +349,45 @@ export const postAnswer = async (data, setToast) => { throw new Error("Failed to post answer"); } } + +export const upvoteAnswer = async (answerId, handleToast) => { + try { + const response = await fetch(`${END_POINT}/answers/upvote`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + credentials: "include", + body: JSON.stringify({ answerId }), + }); + if (!response.ok) { + throw new Error("Failed to upvote question"); + } + showToast(handleToast, "Upvote Successfully"); + return response.json(); + } catch (error) { + showToast(handleToast, "You have already voted", "error"); + throw new Error("Failed to upvote answer"); + } +} + +export const downvoteAnswer = async(answerId, handleToast) => { + try { + const response = await fetch(`${END_POINT}/answers/downvote`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + }, + credentials: "include", + body: JSON.stringify({ answerId }), + }); + if (!response.ok) { + throw new Error("Failed to downvote question"); + } + showToast(handleToast, "Downvote Successfully"); + return response.json(); + } catch (error) { + showToast(handleToast, "You have already voted", "error"); + throw new Error("Failed to downvote answer"); + } +} \ No newline at end of file