Merge branch 'bounties' into dev
This commit is contained in:
commit
c9321494ec
7 changed files with 151 additions and 8 deletions
|
|
@ -7,11 +7,12 @@ import {
|
|||
ListItemText,
|
||||
Tooltip,
|
||||
Typography,
|
||||
TextField
|
||||
} from '@mui/material';
|
||||
import ShareIcon from '@mui/icons-material/Share';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Markdown } from 'components';
|
||||
import { CreationInfoTag } from 'controllers';
|
||||
import { CommentControl, VoteControl } from 'controllers/QAControllers';
|
||||
|
|
@ -51,8 +52,21 @@ export default function Question({
|
|||
getVote,
|
||||
updateVote,
|
||||
postComment,
|
||||
canBounty,
|
||||
handleBounty,
|
||||
hasBounty,
|
||||
show,
|
||||
}) {
|
||||
const min = 75;
|
||||
const max = 500;
|
||||
const [value, setValue] = useState(75);
|
||||
canBounty = canBounty && !hasBounty
|
||||
|
||||
function handleBountyFix(){
|
||||
handleBounty(value)
|
||||
}
|
||||
return (
|
||||
|
||||
<>
|
||||
<Box m={2}>
|
||||
<Typography variant='h4'>{title}</Typography>
|
||||
|
|
@ -121,11 +135,54 @@ export default function Question({
|
|||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
title={canBounty ? '' : 'You must be level 4 and this question must be open or protected'}
|
||||
>
|
||||
<span>
|
||||
|
||||
<Button
|
||||
disabled={!canBounty}
|
||||
style={{ marginLeft: '10px' }}
|
||||
display='inline'
|
||||
m={1}
|
||||
variant='contained'
|
||||
onClick={handleBountyFix}
|
||||
>
|
||||
Add Bounty
|
||||
|
||||
</Button>
|
||||
{ show &&
|
||||
<TextField value = {value} size = "small" type = "number" inputProps={{min,max}} disabled = {!canBounty} label="bounty" onChange={(e) => {
|
||||
if (e.target.value === "") {
|
||||
setValue(75);
|
||||
return;
|
||||
}
|
||||
const value = e.target.value;
|
||||
if (value > max) {
|
||||
setValue(max);
|
||||
} else if (value < min) {
|
||||
setValue(min);
|
||||
} else {
|
||||
setValue(value);
|
||||
}
|
||||
|
||||
|
||||
}}/>
|
||||
}
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
{ongoingVote.users.length > 0 && (
|
||||
<Typography>
|
||||
{ongoingVote.users.toString()} - voting to {ongoingVote.type} this question{' '}
|
||||
</Typography>
|
||||
)}
|
||||
{hasBounty && (
|
||||
<Typography>
|
||||
there is a {hasBounty} point bounty on this question
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
</Box>
|
||||
<Divider />
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import { Question } from 'components/QAComponents';
|
|||
import { useQuestion, useUser } from 'contexts';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import getPermissions from 'services/getPermissions';
|
||||
|
||||
import {
|
||||
addBounty,
|
||||
closeQuestion,
|
||||
getQuestionVote,
|
||||
openQuestion,
|
||||
|
|
@ -13,17 +15,18 @@ import {
|
|||
updateQuestion,
|
||||
updateQuestionVote,
|
||||
} from 'services/questionsServices';
|
||||
import { SettingsPhoneTwoTone } from '@mui/icons-material';
|
||||
|
||||
export default function QuestionController() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
let canBounty = false;
|
||||
const { question_id } = useParams();
|
||||
const { questionData } = useQuestion();
|
||||
const { protect, close, reopen, status } = questionData;
|
||||
const { userData } = useUser();
|
||||
const [ongoingVote, setOngoingVote] = useState({ users: [], type: 'none' });
|
||||
const { permissions, setPermissions } = useQuestion();
|
||||
|
||||
const [show, setShow] = useState(false)
|
||||
function setVote() {
|
||||
if (!questionData.loading) {
|
||||
if (protect.length) setOngoingVote({ users: protect, type: 'protect' });
|
||||
|
|
@ -34,7 +37,9 @@ export default function QuestionController() {
|
|||
|
||||
useEffect(() => {
|
||||
const permissions = getPermissions(questionData, userData, ongoingVote);
|
||||
|
||||
if(userData.username)canBounty = (userData.level >= 4 && status !== 'closed' && !questionData.hasBounty && !questionData.hasAcceptedAnswer)
|
||||
|
||||
permissions.canBounty = canBounty;
|
||||
setPermissions(permissions);
|
||||
setVote();
|
||||
}, [userData, questionData]);
|
||||
|
|
@ -72,6 +77,17 @@ export default function QuestionController() {
|
|||
ongoingVoteSet(reopen, 'reopen');
|
||||
}
|
||||
}
|
||||
function handleBounty(amount){
|
||||
if(show){
|
||||
const data = {user : userData.username, hasAcceptedAnswer: questionData.hasAcceptedAnswer , amount: amount , isOpen: (questionData.status !== 'closed') , hasBounty: questionData.hasBounty }
|
||||
addBounty(question_id, data)
|
||||
questionData.hasBounty = amount;
|
||||
userData.points -= amount;
|
||||
setShow(false)
|
||||
}else{
|
||||
setShow(true)
|
||||
}
|
||||
}
|
||||
|
||||
const getVote = () => getQuestionVote(question_id);
|
||||
const updateVote = (data) => updateQuestionVote(question_id, data);
|
||||
|
|
@ -93,6 +109,9 @@ export default function QuestionController() {
|
|||
getVote,
|
||||
updateVote,
|
||||
postComment,
|
||||
show,
|
||||
handleBounty
|
||||
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,13 @@ const postQuestion = async (data) => callQuestionsAPI('post', ``, data);
|
|||
|
||||
const getQuestion = async (question_id) => callQuestionsAPI('get', `/${question_id}`);
|
||||
|
||||
const addBounty = async (question_id,data) => callQuestionsAPI(
|
||||
'patch',
|
||||
`/${question_id}/addBounty`,
|
||||
data
|
||||
|
||||
)
|
||||
|
||||
const updateQuestion = async (question_id, data) =>
|
||||
callQuestionsAPI('patch', `/${question_id}`, data);
|
||||
|
||||
|
|
@ -103,4 +110,5 @@ export {
|
|||
closeQuestion,
|
||||
openQuestion,
|
||||
protectQuestion,
|
||||
addBounty
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const Question = mongoose.Schema(
|
|||
title: { type: String, required: true },
|
||||
upvotes: { type: Number, required: true, default: 0 },
|
||||
views: { type: Number, required: true, default: 0 },
|
||||
hasBounty: {type: Number, required: false}
|
||||
},
|
||||
{ timestamps: { createdAt: false, updatedAt: true } }
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ const router = express.Router();
|
|||
|
||||
const tokenAuth = require('server/middleware/tokenAuth');
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
const HandleBounty = require('./question/HandleBounty');
|
||||
>>>>>>> bounties
|
||||
const CreateAnswer = require('./question/CreateAnswer');
|
||||
const CreateAnswerComment = require('./question/CreateAnswerComment');
|
||||
const CreateComment = require('./question/CreateComment');
|
||||
|
|
@ -31,7 +35,7 @@ const GetQuestionVote = require('./question/GetQuestionVote');
|
|||
const Search = require('./question/Search');
|
||||
|
||||
router.get('/search', Search);
|
||||
|
||||
router.patch('/:question_id/addBounty', HandleBounty);
|
||||
router.post('/', tokenAuth, CreateQuestion);
|
||||
router.get('/:question_id', GetQuestion);
|
||||
router.patch('/:question_id', tokenAuth, EditQuestion);
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ async function EditAnswerAccepted(req, res) {
|
|||
|
||||
// Find question and verify that it exists
|
||||
const question = await getQuestion(question_id);
|
||||
|
||||
if (!question) return res.status(404).send(config.errorNotFound);
|
||||
|
||||
const {hasBounty} = question;
|
||||
// Verify user owns question and question does not already have an accepted answer
|
||||
if (question.creator !== user.username || question.hasAccepted)
|
||||
return res.status(403).send(config.errorForbidden);
|
||||
|
|
@ -41,8 +42,11 @@ async function EditAnswerAccepted(req, res) {
|
|||
operation: 'increment',
|
||||
amount: 15,
|
||||
});
|
||||
await User.findOneAndUpdate({ username: cachedAnswer.creator }, { $inc: { points: 15 } });
|
||||
|
||||
if(hasBounty){
|
||||
await User.findOneAndUpdate({ username: cachedAnswer.creator }, { $inc: { points: 15 + hasBounty } });
|
||||
}else{
|
||||
await User.findOneAndUpdate({ username: cachedAnswer.creator }, { $inc: { points: 15} });
|
||||
}
|
||||
return res.sendStatus(200);
|
||||
}
|
||||
|
||||
|
|
|
|||
50
server/routes/question/HandleBounty.js
Normal file
50
server/routes/question/HandleBounty.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
const Question = require('server/db/models/Question')
|
||||
const User = require('server/db/models/User')
|
||||
const createRequest = require('server/utils/api');
|
||||
async function HandleBounty(req,res){
|
||||
const {user, hasAcceptedAnswer, amount, isOpen, hasBounty} = req.body;
|
||||
const {question_id} = req.params;
|
||||
|
||||
|
||||
const userPull = await createRequest(
|
||||
'get',
|
||||
`/users/${user}`
|
||||
);
|
||||
|
||||
const questionPull = await createRequest(
|
||||
'get',
|
||||
`/questions/${question_id}`
|
||||
);
|
||||
const question = questionPull.question;
|
||||
|
||||
if(userPull.success){
|
||||
const {points} = userPull.user;
|
||||
if(points >= 75 && isOpen && !hasAcceptedAnswer && !hasBounty){
|
||||
if(amount >= 75 && amount <= 500){
|
||||
if(points - amount >= 75){
|
||||
//updates question
|
||||
await Question.findByIdAndUpdate(question_id, {hasBounty : amount});
|
||||
//updates user in api
|
||||
await createRequest('patch', `/users/${user}/points`, {
|
||||
operation: 'decrement',
|
||||
amount: amount,
|
||||
});
|
||||
//updates user in db
|
||||
await User.findOneAndUpdate(
|
||||
{ username: user },
|
||||
{ $inc: { points: -1 * amount } }
|
||||
);
|
||||
}else{
|
||||
return res.status(403).send("This operation would leave you with less than 75 points")
|
||||
}
|
||||
}else{
|
||||
return res.status(403).send("Must be between 75 and 500 points")
|
||||
}
|
||||
}else{
|
||||
return res.status(403).send("Need to be level 4")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = HandleBounty;
|
||||
Reference in a new issue