Skip to content

Commit

Permalink
Merge branch 'HITK-TECH-Community:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
BHS-Harish authored Aug 6, 2024
2 parents 7a6abd0 + 526b35e commit 65f9789
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 72 deletions.
4 changes: 4 additions & 0 deletions backend/app/models/answers.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ const answerSchema = new Schema(
type: Number,
default: 0,
},
downvotes:{
type:Number,
default:0
},
created_on: {
type: Date,
required: true,
Expand Down
5 changes: 5 additions & 0 deletions backend/app/models/question.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ const questionSchema = new Schema(
downvotes:{
type:Number,
default:0
},
created_by:{
type:String,
required:true,
trim:true
}
},
{ timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' } }
Expand Down
24 changes: 4 additions & 20 deletions backend/app/routes/Q&A/answers/downvoteAnswer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,11 @@ const to = require('await-to-js').default;
const answer = require('../../../models/answers');
const { ErrorHandler } = require('../../../../helpers/error');
const constants = require('../../../../constants');
const { getVoteCookieName } = require('../../../../helpers/middlewares/cookie');

module.exports = async (req, res, next) => {
const { answerId } = req.body;
const [err] = await to(
answer.updateOne({ _id: answerId }, [
{
$set: {
upvotes: {
$cond: [
{
$gt: ['$upvotes', 0],
},
{
$subtract: ['$upvotes', 1],
},
0,
],
},
},
},
])
);

const [err] = await to(answer.updateOne({ _id: answerId }, { $inc: { downvotes: 1 } }));
if (err) {
const error = new ErrorHandler(constants.ERRORS.DATABASE, {
statusCode: 500,
Expand All @@ -35,6 +17,8 @@ module.exports = async (req, res, next) => {
return next(error);
}

res.cookie(getVoteCookieName('answer', answerId), true, { maxAge: 20 * 365 * 24 * 60 * 60 * 1000,sameSite:"none",secure:true });

res.status(200).send({
message: 'Answer has been down voted',
});
Expand Down
5 changes: 3 additions & 2 deletions backend/app/routes/Q&A/answers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const downvoteAnswer = require('./downvoteAnswer');
const updateAnswerStatus = require('./updateAnswerStatus');
const { authMiddleware } = require('../../../../helpers/middlewares/auth');
const deleteAnswer = require('./deleteAnswer');
const { checkVoteCookie } = require('../../../../helpers/middlewares/cookie');

// POST API FOR ANSWER
router.post('/', validation(answerValidationSchema), postAnswer);
Expand All @@ -17,10 +18,10 @@ router.post('/', validation(answerValidationSchema), postAnswer);
router.get('/:questionId', getAnswers);

// INCREASE UPVOTE FOR ANSWERS
router.patch('/upvote', upvoteAnswer);
router.patch('/upvote', checkVoteCookie,upvoteAnswer);

// DECREASE UPVOTE FOR ANSWERS
router.patch('/downvote', downvoteAnswer);
router.patch('/downvote', checkVoteCookie,downvoteAnswer);

// Update Answer Status
router.patch('/updateStatus', validation(updateAnswerStatusSchema), updateAnswerStatus);
Expand Down
3 changes: 3 additions & 0 deletions backend/app/routes/Q&A/answers/upvoteAnswer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const to = require('await-to-js').default;
const answer = require('../../../models/answers');
const { ErrorHandler } = require('../../../../helpers/error');
const constants = require('../../../../constants');
const { getVoteCookieName } = require('../../../../helpers/middlewares/cookie');

module.exports = async (req, res, next) => {
const { answerId } = req.body;
Expand All @@ -16,6 +17,8 @@ module.exports = async (req, res, next) => {
return next(error);
}

res.cookie(getVoteCookieName('answer', answerId), true, { maxAge: 20 * 365 * 24 * 60 * 60 * 1000,sameSite:"none",secure:true });

res.status(200).send({
message: 'Answer has been upvoted',
});
Expand Down
1 change: 1 addition & 0 deletions backend/app/routes/Q&A/question/@validationSchema/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const QuestionValidationSchema = Joi.object().keys({
title: Joi.string().trim().required().min(5),
description: Joi.string().trim().required().min(10),
tags: Joi.array().required(),
created_by:Joi.string().trim().required().min(5)
});

const updateQuestionStatusSchema = Joi.object().keys({
Expand Down
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
Loading

0 comments on commit 65f9789

Please sign in to comment.