Skip to content

Commit

Permalink
Upvote/downvote functionality added in frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
BHS-Harish committed Aug 6, 2024
1 parent 7a6abd0 commit 94a7451
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 50 deletions.
117 changes: 72 additions & 45 deletions frontend/src/pages/Q&A/AnswerModel/AnswerModel.jsx
Original file line number Diff line number Diff line change
@@ -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" },
Expand All @@ -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 (
<div>
{toast.toastStatus && (
<SimpleToast
open={toast.toastStatus}
message={toast.toastMessage}
handleCloseToast={()=>{setToast({toastMessage:"",toastStatus:false,toastType:""})}}
handleCloseToast={() => { setToast({ toastMessage: "", toastStatus: false, toastType: "" }) }}
severity={toast.toastType}
/>
)}
Expand All @@ -75,18 +87,18 @@ export function AnswerModel(props) {
}}
>
<Fade in={props.open}>
<div className={"modal-container"}>
<div className={"modal-container"} style={{ background: dark ? "#171717" : "white" }}>
<div className="close-icon-container">
<span onClick={() => {
<span onClick={() => {
setAnswer("")
props.handleClose(false)
}}>
<i class="fas fa-times close-icon"></i>
}}>
<i class="fas fa-times close-icon" style={{ color: dark && "white"}}></i>
</span>
</div>
<h2 className="ques-title">{props.data?.title}</h2>
<p className="ques-description">{props.data?.description}</p>
<div className="tag-container">
<h2 className="ques-title" style={{ color: dark && "#69a9dd"}}>{props.data?.title}</h2>
<p className="ques-description" style={{ color: dark && "white"}}>{props.data?.description}</p>
<div className="tag-container" style={{ color: dark && "white"}}>
{
props && props.data?.tags?.map((tag, index) => {
if (tag)
Expand All @@ -97,29 +109,44 @@ export function AnswerModel(props) {
}
</div>
<form className="answer-form" onSubmit={handleSubmit}>
<input className="answer-field" onChange={(e) => { setAuthor(e.target.value) }} value={author} type="text" placeholder="Your Name" />
<input className="answer-field" onChange={(e) => { setAnswer(e.target.value) }} value={answer} type="text" placeholder="Post your answer" />
<button className="post-answer-btn">Post</button>
<button className="post-answer-btn" style={{ backgroundColor: dark && "#69a9dd",color:dark&&"#000"}}>Post</button>
</form>
<h3 className="answer-title">Answers ({answers.length})</h3>
<h3 className="answer-title" style={{ color: dark && "#69a9dd"}}>Answers ({answers.length})</h3>
{
answers.length==0?
<p>No answers found...</p>
:
<div>
{
answers.map((ans,index)=>{
return(
<div className="answer-container">
<div className="answer-header">
<h5>{ans.created_by}</h5>
<p>{timeStampFormatter(ans.created_on)}</p>
answers.length == 0 ?
<p style={{ color: dark && "white"}}>No answers found...</p>
:
<div>
{
answers.map((ans, index) => {
return (
<div className="answer-container" style={{ color: dark && "white"}}>
<div className="answer-header">
<h5>{ans.created_by || "Anonymous"}</h5>
<p>{timeStampFormatter(ans.created_on)}</p>
</div>
<p>{ans.answer}</p>
<div>
<button
className="vote-btn"
onClick={() => handleUpvote(ans._id)}
>
πŸ‘{ans.upvotes||0}
</button>
<button
className="vote-btn"
onClick={() => handleDownvote(ans._id)}
>
πŸ‘Ž {ans?.downvotes||0}
</button>
</div>
</div>
<p>{ans.answer}</p>
</div>
)
})
}
</div>
)
})
}
</div>
}
</div>
</Fade>
Expand Down
20 changes: 19 additions & 1 deletion frontend/src/pages/Q&A/AnswerModel/AnswerModel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@

p {
margin-top: 8px;
margin-bottom: 8px;
font-size: 16px;
}
}
Expand All @@ -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 {
Expand All @@ -127,6 +139,12 @@
}

.post-answer-btn {
width: 25%;
width: 100%;
}
.answer-form{
flex-direction: column;
}
.answer-field{
width: 100%;
}
}
35 changes: 34 additions & 1 deletion frontend/src/pages/Q&A/Q&A.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ function Ques(props) {
title: "",
description: "",
tags: [],
created_by:""
});

const handleCloseToast = () => {
Expand Down Expand Up @@ -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 = () => {
Expand Down Expand Up @@ -188,6 +190,9 @@ function Ques(props) {
</span>
))}
</div>
<div>
<p className="question-author">- {item?.created_by||"Anonymous"}</p>
</div>
</div>
<div className="card-down">
<div>
Expand All @@ -214,7 +219,7 @@ function Ques(props) {
}}>Answers</button>
</div>
)
)};
)}
</div>
}
{toast.toastStatus && (
Expand Down Expand Up @@ -251,6 +256,34 @@ function Ques(props) {
</h3>
<div className={style["inside-resource"]}>
<div className="question-inputs">
<div className={`form-group ${style["form-group"]}`}>
<div
className={
dark
? `${style["resource-input"]} ${style["resource-input-dark"]}`
: `${style["resource-input"]} ${style["resource-input-light"]}`
}
>
<input
autoFocus="on"
placeholder="Your Name"
type="text"
name="created_by"
value={formdata.created_by}
onChange={handleChange}
/>
<i className="fas fa-heading"></i>
<div
className={`${style["validation"]} validation d-sm-none d-md-block`}
>
{formerrors["title"] ? (
<div>* {formerrors["title"]}</div>
) : (
<div>&nbsp; &nbsp;</div>
)}
</div>
</div>
</div>
<div className={`form-group ${style["form-group"]}`}>
<div
className={
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/pages/Q&A/Ques.scss
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,9 @@
border-radius: 8px;
font-weight: 600;
font-size: 12px;
}
.question-author{
width: 100%;
padding-top: 10px;
text-align: right;
}
48 changes: 45 additions & 3 deletions frontend/src/service/Faq.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ export const downvote = async (questionId, handleToast) => {

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",
Expand All @@ -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;
Expand All @@ -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");
}
}

0 comments on commit 94a7451

Please sign in to comment.