From ea82c5dc85ab6ba20a7cd454082343ed55aa02ed Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 18 Aug 2022 11:00:52 -0400 Subject: [PATCH 01/58] Added mail branch fix --- server/db/models/Badge.js | 0 server/db/models/Mail.js | 1 + server/routes/mail/Read.js | 14 ++++++++++++++ 3 files changed, 15 insertions(+) create mode 100644 server/db/models/Badge.js create mode 100644 server/routes/mail/Read.js diff --git a/server/db/models/Badge.js b/server/db/models/Badge.js new file mode 100644 index 0000000..e69de29 diff --git a/server/db/models/Mail.js b/server/db/models/Mail.js index e950d31..ac19be9 100644 --- a/server/db/models/Mail.js +++ b/server/db/models/Mail.js @@ -8,6 +8,7 @@ const Mail = mongoose.Schema( sender: { type: String, required: true }, subject: { type: String, required: true }, text: { type: String, required: true }, + read: { type: Boolean, required: true, default: false }, }, { timestamps: false } ); diff --git a/server/routes/mail/Read.js b/server/routes/mail/Read.js new file mode 100644 index 0000000..10d02ee --- /dev/null +++ b/server/routes/mail/Read.js @@ -0,0 +1,14 @@ +const createRequest = require('server/utils/api'); +const deriveKeyFromPassword = require('server/utils/auth'); +const Mail = require('server/db/models/Mail'); +const config = require('server/config.json'); + +async function Read(req, res) { + const { id } = req.body; + + const cachedMail = await Mail.findByIdAndUpdate(id, { read: true }); + + return cachedMail ? res.sendStatus(200) : res.status(404).send(config.errorNotFound); +} + +module.exports = Edit; From 5c955b7a88334d6bd7d4e72c26570350ce07031e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 11:07:55 -0400 Subject: [PATCH 02/58] share question --- .../src/components/QAComponents/Question.jsx | 15 ++++++ package-lock.json | 50 +++++++++++++++++++ package.json | 3 ++ 3 files changed, 68 insertions(+) diff --git a/client/src/components/QAComponents/Question.jsx b/client/src/components/QAComponents/Question.jsx index fa30fbf..b55a58f 100644 --- a/client/src/components/QAComponents/Question.jsx +++ b/client/src/components/QAComponents/Question.jsx @@ -8,6 +8,8 @@ import { Tooltip, Typography, } from '@mui/material'; +import ShareIcon from '@mui/icons-material/Share'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; import { Link } from 'react-router-dom'; import { Markdown } from 'components'; @@ -39,6 +41,7 @@ export default function Question({ text, upvotes, views, + question_id, ongoingVote, canClose, canProtect, @@ -118,6 +121,18 @@ export default function Question({ + + + + {ongoingVote.users.length > 0 && ( {ongoingVote.users.toString()} - voting to {ongoingVote.type} this question{' '} diff --git a/package-lock.json b/package-lock.json index 7c2964e..60df64f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,9 @@ "client", "server" ], + "dependencies": { + "react-copy-to-clipboard": "^5.1.0" + }, "devDependencies": { "concurrently": "^7.3.0" } @@ -6566,6 +6569,14 @@ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" }, + "node_modules/copy-to-clipboard": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz", + "integrity": "sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==", + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, "node_modules/core-js": { "version": "3.24.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz", @@ -16545,6 +16556,18 @@ "node": ">=14" } }, + "node_modules/react-copy-to-clipboard": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", + "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", + "dependencies": { + "copy-to-clipboard": "^3.3.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^15.3.0 || 16 || 17 || 18" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -18943,6 +18966,11 @@ "node": ">=8.0" } }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -25205,6 +25233,14 @@ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" }, + "copy-to-clipboard": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.2.tgz", + "integrity": "sha512-Vme1Z6RUDzrb6xAI7EZlVZ5uvOk2F//GaxKUxajDqm9LhOVM1inxNAD2vy+UZDYsd0uyA9s7b3/FVZPSxqrCfg==", + "requires": { + "toggle-selection": "^1.0.6" + } + }, "core-js": { "version": "3.24.1", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz", @@ -32233,6 +32269,15 @@ "whatwg-fetch": "^3.6.2" } }, + "react-copy-to-clipboard": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.1.0.tgz", + "integrity": "sha512-k61RsNgAayIJNoy9yDsYzDe/yAZAzEbEgcz3DZMhF686LEyukcE1hzurxe85JandPUG+yTfGVFzuEw3xt8WP/A==", + "requires": { + "copy-to-clipboard": "^3.3.1", + "prop-types": "^15.8.1" + } + }, "react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -34023,6 +34068,11 @@ "is-number": "^7.0.0" } }, + "toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==" + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", diff --git a/package.json b/package.json index 98f570a..1b66d94 100644 --- a/package.json +++ b/package.json @@ -30,5 +30,8 @@ ], "devDependencies": { "concurrently": "^7.3.0" + }, + "dependencies": { + "react-copy-to-clipboard": "^5.1.0" } } From ce92585eb7ac4b3fdec42d5ca4c0490d953d5d35 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 18 Aug 2022 11:21:31 -0400 Subject: [PATCH 03/58] Added changes --- server/db/models/Badge.js | 18 ++++++ server/db/models/Question.js | 7 ++- server/routes/mail.js | 2 + server/routes/mail/Read.js | 4 +- server/routes/question.js | 21 +++++-- server/routes/question/EditQuestionBody.js | 66 ++++++++++++++++++++++ 6 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 server/routes/question/EditQuestionBody.js diff --git a/server/db/models/Badge.js b/server/db/models/Badge.js index e69de29..7519a43 100644 --- a/server/db/models/Badge.js +++ b/server/db/models/Badge.js @@ -0,0 +1,18 @@ +const mongoose = require('mongoose'); + +const Answer = mongoose.Schema( + { + question_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Question' }, + answer_id: { type: String, required: true }, + creator: { type: String, required: true }, + text: { type: String, required: true }, + upvotes: { type: Number, required: true, default: 0 }, + downvotes: { type: Number, required: true, default: 0 }, + accepted: { type: Boolean, required: true, default: false }, + createdAt: { type: Date, required: true }, + lastCommentFetch: { type: Date, default: new Date(0) }, + }, + { timestamps: { createdAt: false, updatedAt: true } } +); + +module.exports = mongoose.model('Answer', Answer); diff --git a/server/db/models/Question.js b/server/db/models/Question.js index af65876..3ddb69a 100644 --- a/server/db/models/Question.js +++ b/server/db/models/Question.js @@ -3,7 +3,6 @@ const mongoose = require('mongoose'); const Question = mongoose.Schema( { answers: { type: Number, required: true, default: 0 }, - close: [String], comments: { type: Number, required: true, default: 0 }, createdAt: { type: Date, required: true, default: Date.now }, creator: { type: String, required: true }, @@ -12,8 +11,12 @@ const Question = mongoose.Schema( lastAnswerFetch: { type: Date, default: new Date(0) }, lastCommentFetch: { type: Date, default: new Date(0) }, protect: [String], - question_id: { type: String, required: true }, reopen: [String], + close: [String], + edit: [String], + editText: { type: String }, + tags: [String], + question_id: { type: String, required: true }, status: { type: String, required: true, default: 'open' }, text: { type: String, required: true }, title: { type: String, required: true }, diff --git a/server/routes/mail.js b/server/routes/mail.js index fc8570c..310057a 100644 --- a/server/routes/mail.js +++ b/server/routes/mail.js @@ -5,8 +5,10 @@ const tokenAuth = require('server/middleware/tokenAuth'); const GetMail = require('./mail/Get'); const SendMail = require('./mail/Send'); +const ReadMail = require('./mail/Read'); router.get('/', tokenAuth, GetMail); router.post('/', tokenAuth, SendMail); +router.patch('/', tokenAuth, ReadMail); module.exports = router; diff --git a/server/routes/mail/Read.js b/server/routes/mail/Read.js index 10d02ee..371b2c7 100644 --- a/server/routes/mail/Read.js +++ b/server/routes/mail/Read.js @@ -1,5 +1,3 @@ -const createRequest = require('server/utils/api'); -const deriveKeyFromPassword = require('server/utils/auth'); const Mail = require('server/db/models/Mail'); const config = require('server/config.json'); @@ -11,4 +9,4 @@ async function Read(req, res) { return cachedMail ? res.sendStatus(200) : res.status(404).send(config.errorNotFound); } -module.exports = Edit; +module.exports = Read; diff --git a/server/routes/question.js b/server/routes/question.js index 1b07e60..2285d43 100644 --- a/server/routes/question.js +++ b/server/routes/question.js @@ -3,7 +3,6 @@ const router = express.Router(); const tokenAuth = require('server/middleware/tokenAuth'); - const CreateAnswer = require('./question/CreateAnswer'); const CreateAnswerComment = require('./question/CreateAnswerComment'); const CreateComment = require('./question/CreateComment'); @@ -16,6 +15,7 @@ const EditAnswerCommentVote = require('./question/EditAnswerCommentVote'); const EditAnswerVote = require('./question/EditAnswerVote'); const EditCommentVote = require('./question/EditCommentVote'); const EditQuestion = require('./question/EditQuestion'); +const EditQuestionBody = require('./question/EditQuestionBody'); const EditQuestionStatusClosed = require('./question/EditQuestionStatusClosed'); const EditQuestionStatusProtected = require('./question/EditQuestionStatusProtected'); const EditQuestionStatusReopened = require('./question/EditQuestionStatusReopened'); @@ -35,6 +35,7 @@ router.get('/search', Search); router.post('/', tokenAuth, CreateQuestion); router.get('/:question_id', GetQuestion); router.patch('/:question_id', tokenAuth, EditQuestion); +router.patch('/:question_id/edit', tokenAuth, EditQuestionBody); router.patch('/:question_id/close', tokenAuth, EditQuestionStatusClosed); router.patch('/:question_id/protect', tokenAuth, EditQuestionStatusProtected); router.patch('/:question_id/reopen', tokenAuth, EditQuestionStatusReopened); @@ -56,8 +57,20 @@ router.patch('/:question_id/answers/:answer_id/vote', tokenAuth, EditAnswerVote) router.get('/:question_id/answers/:answer_id/comments', GetAnswerComments); router.post('/:question_id/answers/:answer_id/comments', tokenAuth, CreateAnswerComment); -router.delete('/:question_id/answers/:answer_id/comments/:comment_id', tokenAuth, DeleteAnswerComment); -router.get('/:question_id/answers/:answer_id/comments/:comment_id/vote', tokenAuth, GetAnswerCommentVote); -router.patch('/:question_id/answers/:answer_id/comments/:comment_id/vote', tokenAuth, EditAnswerCommentVote); +router.delete( + '/:question_id/answers/:answer_id/comments/:comment_id', + tokenAuth, + DeleteAnswerComment +); +router.get( + '/:question_id/answers/:answer_id/comments/:comment_id/vote', + tokenAuth, + GetAnswerCommentVote +); +router.patch( + '/:question_id/answers/:answer_id/comments/:comment_id/vote', + tokenAuth, + EditAnswerCommentVote +); module.exports = router; diff --git a/server/routes/question/EditQuestionBody.js b/server/routes/question/EditQuestionBody.js new file mode 100644 index 0000000..04dd9ac --- /dev/null +++ b/server/routes/question/EditQuestionBody.js @@ -0,0 +1,66 @@ +const config = require('server/config.json'); +const Question = require('server/db/models/Question'); +const { getQuestion, refreshQuestion } = require('server/utils/question'); +const createRequest = require('server/utils/api'); +const getUserLevel = require('server/utils/getUserLevel'); + +async function EditQuestionStatusClosed(req, res) { + const { user } = req; + const { question_id } = req.params; + const { text } = req.body; + + // Verify user has required level + if (getUserLevel(user.points) < 7) { + return res.status(403).send(config.errorForbidden); + } + + // Get cached question and make sure it exists + const question = await getQuestion(question_id); + if (!question) return res.status(404).send(config.errorNotFound); + + // Toggle question vote + if (question.edit.length === 0) { + if (!text) { + return res.status(400).send(config.errorIncomplete); + } + + await Question.findByIdAndUpdate(question_id, { + editText: text, + $push: { edit: user.username }, + }); + + return res.sendStatus(200); + } + + if (question.edit.includes(user.username)) { + await Question.findByIdAndUpdate(question_id, { + $pull: { edit: user.username }, + }); + + return res.sendStatus(200); + } else { + await Question.findByIdAndUpdate(question_id, { + $push: { close: user.username }, + }); + } + + // Patch question status if required + if (question.edit.length === 2) { + const question = await Question.findByIdAndUpdate(question_id, { + edit: [], + text, + }); + + const { success } = await createRequest('patch', `/questions/${question_id}`, { + text, + }); + + return success + ? res.send({ text: question.text }) + : res.status(500).send(config.errorGeneric); + } + + return res.sendStatus(200); +} + +module.exports = EditQuestionStatusClosed; From 52aa37c06c8832a9239c5acc17eece927210d7c1 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 18 Aug 2022 11:29:41 -0400 Subject: [PATCH 04/58] Added tags to createQuestion --- server/routes/question/CreateQuestion.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/server/routes/question/CreateQuestion.js b/server/routes/question/CreateQuestion.js index 606a4e9..0aff3e2 100644 --- a/server/routes/question/CreateQuestion.js +++ b/server/routes/question/CreateQuestion.js @@ -5,10 +5,14 @@ const createRequest = require('server/utils/api'); async function CreateQuestion(req, res) { const user = req.user; - const { title, text } = req.body; + const { title, text, tags } = req.body; if (!title || !text) return res.status(400).send(config.errorIncomplete); + if (tags && tags.length > 5) { + return res.status(400).send(config.errorIncomplete); + } + const { success, question } = await createRequest('post', `/questions`, { creator: user.username, title, @@ -17,7 +21,7 @@ async function CreateQuestion(req, res) { if (!success) return res.status(500).send(config.errorGeneric); - const newQuestion = await Question.create({ ...question, _id: question.question_id }); + const newQuestion = await Question.create({ ...question, tags, _id: question.question_id }); // Increment user points by 1 await createRequest('patch', `/users/${user.username}/points`, { From 1a4802788478e1c47efdde176cbf298ea917d40b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 11:37:10 -0400 Subject: [PATCH 05/58] share answer button --- client/src/components/QAComponents/Answer.jsx | 24 +++++++++++++++--- .../QAComponents/CommentControl.jsx | 3 ++- .../src/components/QAComponents/Question.jsx | 25 +++++++++---------- .../QAControllers/AnswerController.jsx | 8 +++--- 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/client/src/components/QAComponents/Answer.jsx b/client/src/components/QAComponents/Answer.jsx index a1d45f1..7cc1808 100644 --- a/client/src/components/QAComponents/Answer.jsx +++ b/client/src/components/QAComponents/Answer.jsx @@ -1,5 +1,7 @@ import CheckIcon from '@mui/icons-material/Check'; +import ShareIcon from '@mui/icons-material/Share'; import { Button, ButtonGroup, ListItem, ListItemText, Tooltip } from '@mui/material'; +import { CopyToClipboard } from 'react-copy-to-clipboard'; import { Markdown } from 'components'; import { CreationInfoTag } from 'controllers'; @@ -14,12 +16,12 @@ export default function Answer({ downvotes, text, upvotes, + acceptAnswer, canComment, canAccept, + postComment, getVote, updateVote, - postComment, - acceptAnswer, }) { return ( @@ -47,12 +49,26 @@ export default function Answer({ + + + {canAccept && ( diff --git a/client/src/components/QAComponents/CommentControl.jsx b/client/src/components/QAComponents/CommentControl.jsx index 43fb4e4..d8eaa8f 100644 --- a/client/src/components/QAComponents/CommentControl.jsx +++ b/client/src/components/QAComponents/CommentControl.jsx @@ -27,11 +27,12 @@ export default function CommentControl({ color='inherit' disableRipple onClick={toggleShow} + m={1} size='small' startIcon={} style={{ textTransform: 'none' }} variant='text' - > + > Comment diff --git a/client/src/components/QAComponents/Question.jsx b/client/src/components/QAComponents/Question.jsx index b55a58f..82354b5 100644 --- a/client/src/components/QAComponents/Question.jsx +++ b/client/src/components/QAComponents/Question.jsx @@ -41,7 +41,6 @@ export default function Question({ text, upvotes, views, - question_id, ongoingVote, canClose, canProtect, @@ -121,18 +120,6 @@ export default function Question({ - - - - {ongoingVote.users.length > 0 && ( {ongoingVote.users.toString()} - voting to {ongoingVote.type} this question{' '} @@ -156,6 +143,18 @@ export default function Question({ + + + diff --git a/client/src/controllers/QAControllers/AnswerController.jsx b/client/src/controllers/QAControllers/AnswerController.jsx index 62aebdb..fe09960 100644 --- a/client/src/controllers/QAControllers/AnswerController.jsx +++ b/client/src/controllers/QAControllers/AnswerController.jsx @@ -7,12 +7,10 @@ import { } from 'services/questionsServices'; export default function AnswerController({ answer_id, question_id, ...props }) { + const acceptAnswer = () => updateAcceptAnswer(question_id, answer_id); const getVote = () => getAnswerVote(question_id, answer_id); const updateVote = (data) => updateAnswerVote(question_id, answer_id, data); - const postComment = async (data) => { - await postAnswerComment(question_id, answer_id, data); - }; - const acceptAnswer = () => updateAcceptAnswer(question_id, answer_id); + const postComment = async (data) => { await postAnswerComment(question_id, answer_id, data); }; return ( ); From 2cf161c24648e6f032f6ee5aea0389f8086948fe Mon Sep 17 00:00:00 2001 From: tejusk2 <92613406+tejusk2@users.noreply.github.com> Date: Thu, 18 Aug 2022 10:48:02 -0500 Subject: [PATCH 06/58] bounty logic --- server/db/models/Question.js | 1 + server/routes/question.js | 4 +- server/routes/question/EditAnswerAccepted.js | 10 ++-- server/routes/question/HandleBounty.js | 48 ++++++++++++++++++++ 4 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 server/routes/question/HandleBounty.js diff --git a/server/db/models/Question.js b/server/db/models/Question.js index af65876..d621f83 100644 --- a/server/db/models/Question.js +++ b/server/db/models/Question.js @@ -19,6 +19,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 } } ); diff --git a/server/routes/question.js b/server/routes/question.js index 1b07e60..4ec5e5e 100644 --- a/server/routes/question.js +++ b/server/routes/question.js @@ -3,7 +3,7 @@ const router = express.Router(); const tokenAuth = require('server/middleware/tokenAuth'); - +const HandleBounty = require('./question/HandleBounty'); const CreateAnswer = require('./question/CreateAnswer'); const CreateAnswerComment = require('./question/CreateAnswerComment'); const CreateComment = require('./question/CreateComment'); @@ -31,7 +31,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); diff --git a/server/routes/question/EditAnswerAccepted.js b/server/routes/question/EditAnswerAccepted.js index 3d7d8cc..d2249b7 100644 --- a/server/routes/question/EditAnswerAccepted.js +++ b/server/routes/question/EditAnswerAccepted.js @@ -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); } diff --git a/server/routes/question/HandleBounty.js b/server/routes/question/HandleBounty.js new file mode 100644 index 0000000..fad17ee --- /dev/null +++ b/server/routes/question/HandleBounty.js @@ -0,0 +1,48 @@ +const Question = require('server/db/models/Question') + +async function HandleBounty(req,res){ + const {user, hasAcceptedAnswer, amount, isOpen} = req; + 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){ + 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: question.creator }, + { $inc: { points: -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; \ No newline at end of file From 35651204348b73ee554347d2640d9976fd039203 Mon Sep 17 00:00:00 2001 From: anirudhsimhadri Date: Thu, 18 Aug 2022 12:27:25 -0400 Subject: [PATCH 07/58] The Buffet view shows when a question has an accepted answer. (Change 8) --- client/src/components/ListQuestion.jsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/src/components/ListQuestion.jsx b/client/src/components/ListQuestion.jsx index 62b97a4..9fbf637 100644 --- a/client/src/components/ListQuestion.jsx +++ b/client/src/components/ListQuestion.jsx @@ -15,6 +15,7 @@ export default function ListQuestion({ title, upvotes, views, + hasAcceptedAnswer, }) { const sm = useMediaQuery((theme) => theme.breakpoints.only('sm')); const md = useMediaQuery((theme) => theme.breakpoints.only('md')); @@ -41,6 +42,8 @@ export default function ListQuestion({ {text.replace(/<[^>]*>?/gm, '')} + Accepted: {String(hasAcceptedAnswer)} + From 29e780eb90a36ab062392b35371507ebb5de9ecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 12:30:28 -0400 Subject: [PATCH 08/58] finish change 10 --- client/src/components/QAComponents/Answer.jsx | 5 +++-- .../src/components/QAComponents/Question.jsx | 3 ++- client/src/containers/QA.jsx | 1 + .../controllers/PaginatedListController.jsx | 16 +++++++++++++++- .../QAPaginatedListController.jsx | 19 ++++++++++++++++--- 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/client/src/components/QAComponents/Answer.jsx b/client/src/components/QAComponents/Answer.jsx index 7cc1808..d3b9d43 100644 --- a/client/src/components/QAComponents/Answer.jsx +++ b/client/src/components/QAComponents/Answer.jsx @@ -8,8 +8,9 @@ import { CreationInfoTag } from 'controllers'; import { AnswerCommentsList, CommentControl, VoteControl } from 'controllers/QAControllers'; export default function Answer({ - accepted, answer_id, + question_id, + accepted, comments, creator, createdAt, @@ -49,7 +50,7 @@ export default function Answer({ - + + { show && + { + 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); + } + + + }}/> + } + + + {ongoingVote.users.length > 0 && ( {ongoingVote.users.toString()} - voting to {ongoingVote.type} this question{' '} )} + {hasBounty && ( + + there is a {hasBounty} point bounty on this question + + )} + diff --git a/client/src/controllers/QAControllers/QuestionController.jsx b/client/src/controllers/QAControllers/QuestionController.jsx index e27afad..d182891 100644 --- a/client/src/controllers/QAControllers/QuestionController.jsx +++ b/client/src/controllers/QAControllers/QuestionController.jsx @@ -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 + }} /> )} diff --git a/client/src/services/questionsServices.js b/client/src/services/questionsServices.js index f10b55d..6420527 100644 --- a/client/src/services/questionsServices.js +++ b/client/src/services/questionsServices.js @@ -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 }; diff --git a/server/routes/question.js b/server/routes/question.js index 4ec5e5e..277f2e7 100644 --- a/server/routes/question.js +++ b/server/routes/question.js @@ -31,7 +31,7 @@ const GetQuestionVote = require('./question/GetQuestionVote'); const Search = require('./question/Search'); router.get('/search', Search); -router.patch(':question_id/addBounty', HandleBounty); +router.patch('/:question_id/addBounty', HandleBounty); router.post('/', tokenAuth, CreateQuestion); router.get('/:question_id', GetQuestion); router.patch('/:question_id', tokenAuth, EditQuestion); diff --git a/server/routes/question/HandleBounty.js b/server/routes/question/HandleBounty.js index fad17ee..c4b262f 100644 --- a/server/routes/question/HandleBounty.js +++ b/server/routes/question/HandleBounty.js @@ -1,9 +1,11 @@ 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} = req; + const {user, hasAcceptedAnswer, amount, isOpen, hasBounty} = req.body; const {question_id} = req.params; + const userPull = await createRequest( 'get', `/users/${user}` @@ -17,7 +19,7 @@ async function HandleBounty(req,res){ if(userPull.success){ const {points} = userPull.user; - if(points >= 75 && isOpen && !hasAcceptedAnswer){ + if(points >= 75 && isOpen && !hasAcceptedAnswer && !hasBounty){ if(amount >= 75 && amount <= 500){ if(points - amount >= 75){ //updates question @@ -29,8 +31,8 @@ async function HandleBounty(req,res){ }); //updates user in db await User.findOneAndUpdate( - { username: question.creator }, - { $inc: { points: -amount } } + { username: user }, + { $inc: { points: -1 * amount } } ); }else{ return res.status(403).send("This operation would leave you with less than 75 points") From 5ce82e4233803cb1945efccdde75d202461923a7 Mon Sep 17 00:00:00 2001 From: tejusk2 <92613406+tejusk2@users.noreply.github.com> Date: Thu, 18 Aug 2022 13:18:56 -0500 Subject: [PATCH 11/58] fixed merge conflict --- server/routes/question.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/routes/question.js b/server/routes/question.js index 9151821..6783f05 100644 --- a/server/routes/question.js +++ b/server/routes/question.js @@ -3,10 +3,7 @@ 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'); From 48aec0a5312dfeb8448596d6f621973a6ee16395 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 18 Aug 2022 14:20:46 -0400 Subject: [PATCH 12/58] Completed badges --- server/db/models/Answer.js | 8 +++++++ server/db/models/Badge.js | 21 +++++----------- server/db/models/Question.js | 8 +++++++ server/db/models/User.js | 14 +++++++++++ server/routes/question/EditAnswerAccepted.js | 3 ++- .../question/EditQuestionStatusProtected.js | 15 +++++------- .../question/EditQuestionStatusReopened.js | 17 ++++++------- server/routes/user.js | 2 ++ server/routes/user/GetBadges.js | 24 +++++++++++++++++++ server/utils/badges/answerBadges.js | 17 +++++++++++++ server/utils/badges/questionBadges.js | 17 +++++++++++++ server/utils/badges/userBadges.js | 17 +++++++++++++ 12 files changed, 128 insertions(+), 35 deletions(-) create mode 100644 server/routes/user/GetBadges.js create mode 100644 server/utils/badges/answerBadges.js create mode 100644 server/utils/badges/questionBadges.js create mode 100644 server/utils/badges/userBadges.js diff --git a/server/db/models/Answer.js b/server/db/models/Answer.js index 7519a43..ff4ecba 100644 --- a/server/db/models/Answer.js +++ b/server/db/models/Answer.js @@ -1,4 +1,6 @@ const mongoose = require('mongoose'); +const calculateAnswerBadges = require('../../utils/badges/answerBadges'); +const User = require('./User'); const Answer = mongoose.Schema( { @@ -15,4 +17,10 @@ const Answer = mongoose.Schema( { timestamps: { createdAt: false, updatedAt: true } } ); +Answer.post('findOneAndUpdate', (doc) => { + const badges = calculateAnswerBadges(doc.points); + + User.findOneAndUpdate({ username: doc.creator }, { $addToSet: { tags: { $each: badges } } }); +}); + module.exports = mongoose.model('Answer', Answer); diff --git a/server/db/models/Badge.js b/server/db/models/Badge.js index 7519a43..7b9b7b7 100644 --- a/server/db/models/Badge.js +++ b/server/db/models/Badge.js @@ -1,18 +1,9 @@ const mongoose = require('mongoose'); -const Answer = mongoose.Schema( - { - question_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Question' }, - answer_id: { type: String, required: true }, - creator: { type: String, required: true }, - text: { type: String, required: true }, - upvotes: { type: Number, required: true, default: 0 }, - downvotes: { type: Number, required: true, default: 0 }, - accepted: { type: Boolean, required: true, default: false }, - createdAt: { type: Date, required: true }, - lastCommentFetch: { type: Date, default: new Date(0) }, - }, - { timestamps: { createdAt: false, updatedAt: true } } -); +const Badge = mongoose.Schema({ + title: { type: String, required: true }, + text: { type: String, required: true }, + rank: { type: String, required: true }, +}); -module.exports = mongoose.model('Answer', Answer); +module.exports = mongoose.model('Badge', Badge); diff --git a/server/db/models/Question.js b/server/db/models/Question.js index 3ddb69a..c828cf6 100644 --- a/server/db/models/Question.js +++ b/server/db/models/Question.js @@ -1,4 +1,6 @@ const mongoose = require('mongoose'); +const calculateQuestionBadges = require('../../utils/badges/questionBadges'); +const User = require('./User'); const Question = mongoose.Schema( { @@ -26,4 +28,10 @@ const Question = mongoose.Schema( { timestamps: { createdAt: false, updatedAt: true } } ); +Question.post('findOneAndUpdate', (doc) => { + const badges = calculateQuestionBadges(doc.points); + + User.findOneAndUpdate({ username: doc.creator }, { $addToSet: { tags: { $each: badges } } }); +}); + module.exports = mongoose.model('Question', Question); diff --git a/server/db/models/User.js b/server/db/models/User.js index 5a4124c..9b06e22 100644 --- a/server/db/models/User.js +++ b/server/db/models/User.js @@ -1,4 +1,5 @@ const mongoose = require('mongoose'); +const calculateUserBadges = require('server/utils/badges/userBadges'); const User = mongoose.Schema( { @@ -9,8 +10,21 @@ const User = mongoose.Schema( lastMailFetch: { type: Date, default: new Date(0) }, lastAnswerFetch: { type: Date, default: new Date(0) }, user_id: { type: String, required: true, unique: true }, + badges: [String], }, { timestamps: { createdAt: false, updatedAt: true } } ); +User.post('findOneAndUpdate', async (doc) => { + const badges = calculateUserBadges(doc.points); + + console.log(`first: ${doc.badges}`); + + doc.badges = [...new Set([...doc.badges, ...badges])]; + + console.log(doc.badges); + + doc.save(); +}); + module.exports = mongoose.model('User', User); diff --git a/server/routes/question/EditAnswerAccepted.js b/server/routes/question/EditAnswerAccepted.js index 3d7d8cc..f322b2c 100644 --- a/server/routes/question/EditAnswerAccepted.js +++ b/server/routes/question/EditAnswerAccepted.js @@ -14,8 +14,9 @@ async function EditAnswerAccepted(req, res) { if (!question) return res.status(404).send(config.errorNotFound); // Verify user owns question and question does not already have an accepted answer - if (question.creator !== user.username || question.hasAccepted) + if (question.creator !== user.username || question.hasAccepted) { return res.status(403).send(config.errorForbidden); + } // Patch question with BDPA server const patchAnswer = await createRequest( diff --git a/server/routes/question/EditQuestionStatusProtected.js b/server/routes/question/EditQuestionStatusProtected.js index d3ac29a..867e581 100644 --- a/server/routes/question/EditQuestionStatusProtected.js +++ b/server/routes/question/EditQuestionStatusProtected.js @@ -18,10 +18,7 @@ async function EditQuestionStatusProtected(req, res) { if (!question) return res.status(404).send(config.errorNotFound); // Verify question does not have incompatible status - if ( - question.status === 'closed' || - question.status === 'protected' - ) { + if (question.status === 'closed' || question.status === 'protected') { return res.status(400).send({ success: false, error: 'This question is already closed or protected.', @@ -48,11 +45,11 @@ async function EditQuestionStatusProtected(req, res) { status: 'protected', }); - const { success } = await createRequest( - 'patch', - `/questions/${question_id}`, - { status: 'protected' } - ); + await User.findOneAndUpdate({ username: creator }, { $push: { badges: 'Protected' } }); + + const { success } = await createRequest('patch', `/questions/${question_id}`, { + status: 'protected', + }); await Question.findByIdAndUpdate(question_id, { status: 'protected' }); return success diff --git a/server/routes/question/EditQuestionStatusReopened.js b/server/routes/question/EditQuestionStatusReopened.js index 9c4cb1b..4fdab9f 100644 --- a/server/routes/question/EditQuestionStatusReopened.js +++ b/server/routes/question/EditQuestionStatusReopened.js @@ -1,6 +1,6 @@ const config = require('server/config.json'); const Question = require('server/db/models/Question'); -const { getQuestion, refreshQuestion } = require('server/utils/question'); +const { getQuestion } = require('server/utils/question'); const getUserLevel = require('server/utils/getUserLevel'); const createRequest = require('server/utils/api'); @@ -18,10 +18,7 @@ async function EditQuestionStatusReopened(req, res) { if (!question) return res.status(404).send(config.errorNotFound); // Make sure that question has compatible status - if ( - question.status === 'protected' || - question.status === 'open' - ) { + if (question.status === 'protected' || question.status === 'open') { return res.status(400).send({ success: false, error: 'This question is already open.', @@ -48,11 +45,11 @@ async function EditQuestionStatusReopened(req, res) { status: 'open', }); - const { success } = await createRequest( - 'patch', - `/questions/${question_id}`, - { status: 'open' } - ); + await User.findOneAndUpdate({ username: creator }, { $push: { badges: 'Zombie' } }); + + const { success } = await createRequest('patch', `/questions/${question_id}`, { + status: 'open', + }); await Question.findByIdAndUpdate(question_id, { status: 'open' }); return success diff --git a/server/routes/user.js b/server/routes/user.js index e786963..6799b89 100644 --- a/server/routes/user.js +++ b/server/routes/user.js @@ -5,6 +5,7 @@ const basicAuth = require('server/middleware/basicAuth'); const tokenAuth = require('server/middleware/tokenAuth'); const Answers = require('./user/Answers'); +const GetBadges = require('./user/GetBadges'); const Delete = require('./user/Delete'); const Edit = require('./user/Edit'); const GetUser = require('./user/GetUser'); @@ -20,6 +21,7 @@ router.post('/', Register); router.patch('/', tokenAuth, Edit); router.get('/questions', tokenAuth, Questions); router.get('/answers', tokenAuth, Answers); +router.get('/badge', tokenAuth, GetBadges); router.post('/login', basicAuth, Login); router.get('/remember', tokenAuth, Remember); router.delete('/logout', tokenAuth, Logout); diff --git a/server/routes/user/GetBadges.js b/server/routes/user/GetBadges.js new file mode 100644 index 0000000..a7f0e34 --- /dev/null +++ b/server/routes/user/GetBadges.js @@ -0,0 +1,24 @@ +const Badge = require('server/db/models/Badge'); + +async function GetBadges(req, res) { + const { user } = req; + + const badges = await Badge.find(); + + const response = badges.reduce( + (acc, badge) => { + if (user.badges.includes(badge.title)) { + acc.obtained.append(badge); + } else { + acc.unobtained.append(badge); + } + + return acc; + }, + { obtained: [], unobtained: [] } + ); + + return res.send(response); +} + +module.exports = GetBadges; diff --git a/server/utils/badges/answerBadges.js b/server/utils/badges/answerBadges.js new file mode 100644 index 0000000..4a47e1c --- /dev/null +++ b/server/utils/badges/answerBadges.js @@ -0,0 +1,17 @@ +function calculateAnswerBadges(points) { + let badges = []; + + if (points >= 100) { + badges.push('Great Answer'); + } + if (points >= 25) { + badges.push('Good Answer'); + } + if (points >= 10) { + badges.push('Nice Answer'); + } + + return badges; +} + +module.exports = calculateAnswerBadges; diff --git a/server/utils/badges/questionBadges.js b/server/utils/badges/questionBadges.js new file mode 100644 index 0000000..cdf9437 --- /dev/null +++ b/server/utils/badges/questionBadges.js @@ -0,0 +1,17 @@ +function calculateQuestionBadges(points) { + let badges = []; + + if (points >= 100) { + badges.push('Great Question'); + } + if (points >= 25) { + badges.push('Good Question'); + } + if (points >= 10) { + badges.push('Nice Question'); + } + + return badges; +} + +module.exports = calculateQuestionBadges; diff --git a/server/utils/badges/userBadges.js b/server/utils/badges/userBadges.js new file mode 100644 index 0000000..c8bc1f0 --- /dev/null +++ b/server/utils/badges/userBadges.js @@ -0,0 +1,17 @@ +function calculateUserBadges(points) { + let badges = []; + + if (points >= 10000) { + badges.push('Socratic'); + } + if (points >= 3000) { + badges.push('Inquisitive'); + } + if (points >= 100) { + badges.push('Curious'); + } + + return badges; +} + +module.exports = calculateUserBadges; From 424e14bbbdce9e5cef711d2341a72232b22fba72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 14:22:48 -0400 Subject: [PATCH 13/58] dashboard badges --- client/src/services/userServices.js | 47 +---------------------------- 1 file changed, 1 insertion(+), 46 deletions(-) diff --git a/client/src/services/userServices.js b/client/src/services/userServices.js index 686a2a4..faa74b6 100644 --- a/client/src/services/userServices.js +++ b/client/src/services/userServices.js @@ -23,52 +23,7 @@ const getUser = async (username) => callUsersAPI('get', `/${username}`); const getUserAnswers = async () => callUsersAPI('get', `/answers`); -const getUserBadges = async () => ( - { - "badges": { - "obtained": [ - { - "title": "Socratic", - "text": "Have at least 1000 points", - "rank": "Gold" - }, - ], - "unobtained": [ - { - "title": "Socratic", - "text": "Have at least 1000 points", - "rank": "Bronze" - }, - { - "title": "Socratic", - "text": "Have at least 1000 points", - "rank": "Bronze" - }, - { - "title": "Socratic", - "text": "Have at least 1000 points", - "rank": "Bronze" - }, - { - "title": "Socratic", - "text": "Have at least 1000 points", - "rank": "Bronze" - }, - { - "title": "Socratic", - "text": "Have at least 1000 points", - "rank": "Bronze" - }, - { - "title": "Socratic", - "text": "Have at least 1000 points", - "rank": "Bronze" - }, - ] - } - } -) -// callUsersAPI('get', `/badges`); +const getUserBadges = async () => callUsersAPI('get', `/badges`); const getUserQuestions = async () => callUsersAPI('get', `/questions`); From 5f389f52948ecce99c42aebb6a0444a88cf26874 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 18 Aug 2022 14:35:05 -0400 Subject: [PATCH 14/58] Completed badges --- server/badges.json | 64 +++++++++++++++++++++++++++++++++++++++ server/db/models/Badge.js | 2 +- 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 server/badges.json diff --git a/server/badges.json b/server/badges.json new file mode 100644 index 0000000..ddeb065 --- /dev/null +++ b/server/badges.json @@ -0,0 +1,64 @@ +{ + "badges": [ + { + "title": "Great Question", + "text": "Have a question with net total points of >100", + "rank": "Gold" + }, + { + "title": "Good Question", + "text": "Have a question with net total points of >25", + "rank": "Silver" + }, + { + "title": "Nice Question", + "text": "Have a question with net total points of >10", + "rank": "Bronze" + }, + { + "title": "Great Answer", + "text": "Have a answer with net total points of >100", + "rank": "Gold" + }, + { + "title": "Good Answer", + "text": "Have a answer with net total points of >25", + "rank": "Silver" + }, + { + "title": "Nice Answer", + "text": "Have a answer with net total points of >10", + "rank": "Bronze" + }, + { + "title": "Socratic", + "text": "Have at least 10000 points", + "rank": "Gold" + }, + { + "title": "Inquisitive", + "text": "Have at least 3000 points", + "rank": "Silver" + }, + { + "title": "Curious", + "text": "Have at least 100 points", + "rank": "Bronze" + }, + { + "title": "Zombie", + "text": "Have a question that is reopened", + "rank": "Gold" + }, + { + "title": "Protected", + "text": "Have a question that is protected", + "rank": "Silver" + }, + { + "title": "Scholar", + "text": "Accept an answer", + "rank": "Bronze" + } + ] +} diff --git a/server/db/models/Badge.js b/server/db/models/Badge.js index 7b9b7b7..7076d4a 100644 --- a/server/db/models/Badge.js +++ b/server/db/models/Badge.js @@ -3,7 +3,7 @@ const mongoose = require('mongoose'); const Badge = mongoose.Schema({ title: { type: String, required: true }, text: { type: String, required: true }, - rank: { type: String, required: true }, + rank: { type: String, required: true, enum: ['Gold', 'Silver', 'Bronze'] }, }); module.exports = mongoose.model('Badge', Badge); From 30a35fce7fd932a4443a03198466a3100a061660 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 18 Aug 2022 14:49:49 -0400 Subject: [PATCH 15/58] Added badgecount --- server/routes/mail.js | 2 +- server/routes/mail/Read.js | 4 ++-- server/routes/user/GetUser.js | 23 +++++++++++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/server/routes/mail.js b/server/routes/mail.js index 310057a..faa87ef 100644 --- a/server/routes/mail.js +++ b/server/routes/mail.js @@ -9,6 +9,6 @@ const ReadMail = require('./mail/Read'); router.get('/', tokenAuth, GetMail); router.post('/', tokenAuth, SendMail); -router.patch('/', tokenAuth, ReadMail); +router.patch('/:mail_id', tokenAuth, ReadMail); module.exports = router; diff --git a/server/routes/mail/Read.js b/server/routes/mail/Read.js index 371b2c7..c2003a3 100644 --- a/server/routes/mail/Read.js +++ b/server/routes/mail/Read.js @@ -2,9 +2,9 @@ const Mail = require('server/db/models/Mail'); const config = require('server/config.json'); async function Read(req, res) { - const { id } = req.body; + const { mail_id } = req.params; - const cachedMail = await Mail.findByIdAndUpdate(id, { read: true }); + const cachedMail = await Mail.findByIdAndUpdate(mail_id, { read: true }); return cachedMail ? res.sendStatus(200) : res.status(404).send(config.errorNotFound); } diff --git a/server/routes/user/GetUser.js b/server/routes/user/GetUser.js index 25ddd93..7e59490 100644 --- a/server/routes/user/GetUser.js +++ b/server/routes/user/GetUser.js @@ -2,6 +2,7 @@ const createRequest = require('server/utils/api'); const config = require('server/config.json'); const User = require('server/db/models/User'); const getUserLevel = require('server/utils/getUserLevel'); +const Badge = require('server/db/models/'); async function GetUser(req, res) { const { username } = req.params; @@ -27,6 +28,27 @@ async function GetUser(req, res) { new: true, }); + const badges = await Badge.find({ title: { $in: user.badges } }); + + const badgeCount = badges.reduce( + (acc, badge) => { + if (badge.rank === 'Gold') { + acc.gold = acc.gold++; + } else if (badge.rank === 'Silver') { + acc.silver = acc.silver++; + } else if (badge.rank === 'Bronze') { + acc.bronze = acc.bronze++; + } + + return acc; + }, + { + gold: 0, + silver: 0, + bronze: 0, + } + ); + return res.send({ user: { username: newUser.username, @@ -34,6 +56,7 @@ async function GetUser(req, res) { points: newUser.points, level: getUserLevel(newUser.points), }, + badgeCount, }); } From 92b219491b98e01f5b2d5466fe803cb15b0e8459 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 18 Aug 2022 14:53:23 -0400 Subject: [PATCH 16/58] Fixed imports --- server/routes/user/GetUser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routes/user/GetUser.js b/server/routes/user/GetUser.js index 7e59490..b86dc67 100644 --- a/server/routes/user/GetUser.js +++ b/server/routes/user/GetUser.js @@ -2,7 +2,7 @@ const createRequest = require('server/utils/api'); const config = require('server/config.json'); const User = require('server/db/models/User'); const getUserLevel = require('server/utils/getUserLevel'); -const Badge = require('server/db/models/'); +const Badge = require('server/db/models/Badge'); async function GetUser(req, res) { const { username } = req.params; From 019877db9d2e2970c4293182b29c32e24e98010f Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 18 Aug 2022 15:07:15 -0400 Subject: [PATCH 17/58] Modified search --- server/routes/question/Search.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/server/routes/question/Search.js b/server/routes/question/Search.js index eb23464..0d15e46 100644 --- a/server/routes/question/Search.js +++ b/server/routes/question/Search.js @@ -1,14 +1,11 @@ -const createRequest = require('server/utils/api'); const config = require('server/config.json'); const Question = require('server/db/models/Question'); async function Search(req, res) { - const { success, questions } = await createRequest( - 'get', - `/questions/search`, - req.query - ); + const { match, regexMatch, sort } = req.query; + + const questions = await Question.find({}); if (!success) return res.status(500).send(config.errorGeneric); @@ -26,7 +23,7 @@ async function Search(req, res) { }) ); - return res.send({ questions: questionSet.filter(question => question) }); + return res.send({ questions: questionSet.filter((question) => question) }); } module.exports = Search; From b771d54453db01129f419a173281f0242f8021fc Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 18 Aug 2022 15:07:36 -0400 Subject: [PATCH 18/58] Fixed badge --- server/db/models/Answer.js | 2 +- server/db/models/Question.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/db/models/Answer.js b/server/db/models/Answer.js index ff4ecba..45ad843 100644 --- a/server/db/models/Answer.js +++ b/server/db/models/Answer.js @@ -18,7 +18,7 @@ const Answer = mongoose.Schema( ); Answer.post('findOneAndUpdate', (doc) => { - const badges = calculateAnswerBadges(doc.points); + const badges = calculateAnswerBadges(doc.upvotes - doc.downvotes); User.findOneAndUpdate({ username: doc.creator }, { $addToSet: { tags: { $each: badges } } }); }); diff --git a/server/db/models/Question.js b/server/db/models/Question.js index 9661147..9b12cef 100644 --- a/server/db/models/Question.js +++ b/server/db/models/Question.js @@ -24,13 +24,13 @@ 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} + hasBounty: { type: Number, required: false }, }, { timestamps: { createdAt: false, updatedAt: true } } ); Question.post('findOneAndUpdate', (doc) => { - const badges = calculateQuestionBadges(doc.points); + const badges = calculateQuestionBadges(doc.upvotes - doc.downvotes); User.findOneAndUpdate({ username: doc.creator }, { $addToSet: { tags: { $each: badges } } }); }); From bda650f728118321b027831ac878ecc44f8693e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 15:13:29 -0400 Subject: [PATCH 19/58] update --- client/src/components/MailUnit.jsx | 17 +++- client/src/components/Profile.jsx | 3 +- client/src/contexts/UserContext.js | 3 - client/src/controllers/BadgesController.js | 1 + client/src/services/mailServices.js | 9 ++- client/src/services/userServices.js | 94 +++++++++++----------- server/bin/www | 4 +- server/db/models/Answer.js | 7 +- server/db/models/Question.js | 7 +- server/routes/user.js | 2 +- server/routes/user/GetBadges.js | 6 +- 11 files changed, 85 insertions(+), 68 deletions(-) diff --git a/client/src/components/MailUnit.jsx b/client/src/components/MailUnit.jsx index d818e4d..d4c23c2 100644 --- a/client/src/components/MailUnit.jsx +++ b/client/src/components/MailUnit.jsx @@ -3,11 +3,24 @@ import { Accordion, AccordionDetails, AccordionSummary, Box, Typography } from ' import ReactTimeAgo from 'react-time-ago'; import { Markdown } from 'components'; +import { readMail } from 'services/mailServices'; -export default function MailUnit({ sender, createdAt, subject, text }) { +export default function MailUnit({ + mail_id, + sender, + createdAt, + subject, + text, + read +}) { + return ( - }> + } + onClick={() => {readMail(mail_id)}} + sx={read && {backgroundColor: 'lightGray'}} + > From {sender} diff --git a/client/src/components/Profile.jsx b/client/src/components/Profile.jsx index 96b3d93..7c4d5c0 100644 --- a/client/src/components/Profile.jsx +++ b/client/src/components/Profile.jsx @@ -2,7 +2,8 @@ import { IconButton, Typography } from '@mui/material'; import Gravatar from 'react-gravatar'; import { Link } from 'react-router-dom'; -export default function Profile({ userData: { email, level, points } }) { +export default function Profile({ userData: { badges, email, level, points } }) { + return ( <> diff --git a/client/src/contexts/UserContext.js b/client/src/contexts/UserContext.js index 5baf70c..d4ac10a 100644 --- a/client/src/contexts/UserContext.js +++ b/client/src/contexts/UserContext.js @@ -11,9 +11,6 @@ export default function UserProvider({ children }) { const [userData, setUserData] = useState(initialUserData) const navigate = useNavigate(); - - - useEffect(() => { const loadUserData = async () => { const { user, error } = await remember(); diff --git a/client/src/controllers/BadgesController.js b/client/src/controllers/BadgesController.js index 4ab60f8..c8d1c09 100644 --- a/client/src/controllers/BadgesController.js +++ b/client/src/controllers/BadgesController.js @@ -9,6 +9,7 @@ export default function BadgesController() { useEffect(() => { const loadBadges = async () => { const { badges: newBadges } = await getUserBadges(); + console.log(123123, newBadges) setBadges(newBadges); }; loadBadges(); diff --git a/client/src/services/mailServices.js b/client/src/services/mailServices.js index 2dabea8..becd381 100644 --- a/client/src/services/mailServices.js +++ b/client/src/services/mailServices.js @@ -2,8 +2,11 @@ import { createEndpoint } from './api'; const callMailAPI = createEndpoint('/mail'); -const postMail = async (data) => callMailAPI('post', ``, data); - const getMail = async () => callMailAPI('get', ``); -export { getMail, postMail }; +const postMail = async (data) => callMailAPI('post', ``, data); + +const readMail = async (mail_id) => callMailAPI('patch', `/${mail_id}`, { read: 'true' }); + + +export { getMail, postMail, readMail }; diff --git a/client/src/services/userServices.js b/client/src/services/userServices.js index faa74b6..7e9f04f 100644 --- a/client/src/services/userServices.js +++ b/client/src/services/userServices.js @@ -6,17 +6,17 @@ import { createEndpoint } from './api'; const callUsersAPI = createEndpoint('/users'); const getLoginAttempts = () => { - let loginAttempts = Number(Cookies.get('loginAttempts')) || 0; - let loginTimeout = Number(Cookies.get('loginTimeout')) - Date.now() || 0; + let loginAttempts = Number(Cookies.get('loginAttempts')) || 0; + let loginTimeout = Number(Cookies.get('loginTimeout')) - Date.now() || 0; - if (loginTimeout < 0) { - Cookies.remove('loginAttempts'); - Cookies.remove('loginTimeout'); - loginAttempts = 0; - loginTimeout = 0; - } + if (loginTimeout < 0) { + Cookies.remove('loginAttempts'); + Cookies.remove('loginTimeout'); + loginAttempts = 0; + loginTimeout = 0; + } - return { loginAttempts, loginTimeout }; + return { loginAttempts, loginTimeout }; }; const getUser = async (username) => callUsersAPI('get', `/${username}`); @@ -27,28 +27,28 @@ const getUserBadges = async () => callUsersAPI('get', `/badges`); const getUserQuestions = async () => callUsersAPI('get', `/questions`); -const deleteUser = async () => callUsersAPI('delete',`/delete`) +const deleteUser = async () => callUsersAPI('delete', `/delete`) const incrementLoginAttempts = () => { - const { loginAttempts } = getLoginAttempts(); - Cookies.set('loginAttempts', loginAttempts + 1); - if (loginAttempts >= 2) Cookies.set('loginTimeout', Date.now() + 1000 * 60 * 5); + const { loginAttempts } = getLoginAttempts(); + Cookies.set('loginAttempts', loginAttempts + 1); + if (loginAttempts >= 2) Cookies.set('loginTimeout', Date.now() + 1000 * 60 * 5); }; const login = async ({ username, password }) => { - const encoded = Buffer.from(`${username}:${password}`).toString('base64'); - const { token, ...data } = await callUsersAPI( - 'post', - `/login`, - { remember: false }, - `basic ${encoded}` - ); - if (token) { - Cookies.set('token', token); - Cookies.remove('loginAttempts'); - Cookies.remove('loginTimeout'); - } - return data; + const encoded = Buffer.from(`${username}:${password}`).toString('base64'); + const { token, ...data } = await callUsersAPI( + 'post', + `/login`, + { remember: false }, + `basic ${encoded}` + ); + if (token) { + Cookies.set('token', token); + Cookies.remove('loginAttempts'); + Cookies.remove('loginTimeout'); + } + return data; }; const logout = async () => callUsersAPI('post', `/login`, { remember: false }); @@ -58,13 +58,13 @@ const updateUser = async (data) => callUsersAPI('patch', ``, data); const register = async (data) => callUsersAPI('post', ``, data); const remember = async () => { - if (Cookies.get('token')) { - const { user, error } = await callUsersAPI('get', `/remember`); - if (user) - return { user }; - Cookies.remove('token'); - return { error } - } + if (Cookies.get('token')) { + const { user, error } = await callUsersAPI('get', `/remember`); + if (user) + return { user }; + Cookies.remove('token'); + return { error } + } } const requestReset = async (data) => callUsersAPI('post', `/reset`, data); @@ -72,18 +72,18 @@ const requestReset = async (data) => callUsersAPI('post', `/reset`, data); const resetPassword = async (id, data) => callUsersAPI('post', `/reset/${id}`, data); export { - getLoginAttempts, - getUser, - getUserAnswers, - getUserBadges, - getUserQuestions, - incrementLoginAttempts, - login, - logout, - register, - remember, - updateUser, - requestReset, - resetPassword, - deleteUser + getLoginAttempts, + getUser, + getUserAnswers, + getUserBadges, + getUserQuestions, + incrementLoginAttempts, + login, + logout, + register, + remember, + updateUser, + requestReset, + resetPassword, + deleteUser }; diff --git a/server/bin/www b/server/bin/www index d0655af..1ba6662 100644 --- a/server/bin/www +++ b/server/bin/www @@ -43,8 +43,8 @@ mongoose.connection.on( console.error.bind(console, '[ERROR]: MongoDB connection error - ') ); -//cron.schedule('*/30 * * * * *', refreshQuestions); -//cron.schedule('*/2 * * * *', refreshUsers); +// cron.schedule('*/30 * * * * *', refreshQuestions); +// cron.schedule('*/2 * * * *', refreshUsers); server.listen(port, console.log(`Listening on port: ${port}`)); server.on('error', onError); diff --git a/server/db/models/Answer.js b/server/db/models/Answer.js index 45ad843..f705a31 100644 --- a/server/db/models/Answer.js +++ b/server/db/models/Answer.js @@ -18,9 +18,10 @@ const Answer = mongoose.Schema( ); Answer.post('findOneAndUpdate', (doc) => { - const badges = calculateAnswerBadges(doc.upvotes - doc.downvotes); - - User.findOneAndUpdate({ username: doc.creator }, { $addToSet: { tags: { $each: badges } } }); + if (doc) { + const badges = calculateAnswerBadges(doc.upvotes - doc.downvotes); + User.findOneAndUpdate({ username: doc.creator }, { $addToSet: { tags: { $each: badges } } }); + } }); module.exports = mongoose.model('Answer', Answer); diff --git a/server/db/models/Question.js b/server/db/models/Question.js index 9b12cef..68f63a0 100644 --- a/server/db/models/Question.js +++ b/server/db/models/Question.js @@ -30,9 +30,10 @@ const Question = mongoose.Schema( ); Question.post('findOneAndUpdate', (doc) => { - const badges = calculateQuestionBadges(doc.upvotes - doc.downvotes); - - User.findOneAndUpdate({ username: doc.creator }, { $addToSet: { tags: { $each: badges } } }); + if(doc){ + const badges = calculateQuestionBadges(doc.upvotes - doc.downvotes); + User.findOneAndUpdate({ username: doc.creator }, { $addToSet: { tags: { $each: badges } } }); + } }); module.exports = mongoose.model('Question', Question); diff --git a/server/routes/user.js b/server/routes/user.js index 6799b89..eb42caa 100644 --- a/server/routes/user.js +++ b/server/routes/user.js @@ -21,7 +21,7 @@ router.post('/', Register); router.patch('/', tokenAuth, Edit); router.get('/questions', tokenAuth, Questions); router.get('/answers', tokenAuth, Answers); -router.get('/badge', tokenAuth, GetBadges); +router.get('/badges', tokenAuth, GetBadges); router.post('/login', basicAuth, Login); router.get('/remember', tokenAuth, Remember); router.delete('/logout', tokenAuth, Logout); diff --git a/server/routes/user/GetBadges.js b/server/routes/user/GetBadges.js index a7f0e34..fc83dd4 100644 --- a/server/routes/user/GetBadges.js +++ b/server/routes/user/GetBadges.js @@ -8,9 +8,9 @@ async function GetBadges(req, res) { const response = badges.reduce( (acc, badge) => { if (user.badges.includes(badge.title)) { - acc.obtained.append(badge); + acc.obtained.push(badge); } else { - acc.unobtained.append(badge); + acc.unobtained.push(badge); } return acc; @@ -18,7 +18,7 @@ async function GetBadges(req, res) { { obtained: [], unobtained: [] } ); - return res.send(response); + return res.send({ badges: response }); } module.exports = GetBadges; From 285a7ddbef515f58c4bb2558e627ba7bdd7f0690 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 18 Aug 2022 15:19:46 -0400 Subject: [PATCH 20/58] Fixed badgecount --- server/routes/user/GetUser.js | 2 +- server/routes/user/Remember.js | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/server/routes/user/GetUser.js b/server/routes/user/GetUser.js index b86dc67..9e0b720 100644 --- a/server/routes/user/GetUser.js +++ b/server/routes/user/GetUser.js @@ -55,8 +55,8 @@ async function GetUser(req, res) { email: newUser.email, points: newUser.points, level: getUserLevel(newUser.points), + badgeCount, }, - badgeCount, }); } diff --git a/server/routes/user/Remember.js b/server/routes/user/Remember.js index 3c6ea79..1d39ea4 100644 --- a/server/routes/user/Remember.js +++ b/server/routes/user/Remember.js @@ -1,15 +1,38 @@ const getUserLevel = require('server/utils/getUserLevel'); +const Badge = require('../../db/models/Badge'); async function Remember(req, res) { const { user } = req; + const badges = await Badge.find({ title: { $in: user.badges } }); + + const badgeCount = badges.reduce( + (acc, badge) => { + if (badge.rank === 'Gold') { + acc.gold = acc.gold++; + } else if (badge.rank === 'Silver') { + acc.silver = acc.silver++; + } else if (badge.rank === 'Bronze') { + acc.bronze = acc.bronze++; + } + + return acc; + }, + { + gold: 0, + silver: 0, + bronze: 0, + } + ); + return res.send({ user: { username: user.username, email: user.email, points: user.points, level: getUserLevel(user.points), - } + badgeCount, + }, }); } From ed6ed93e2da3d0a8d1db502219dcfe6cabb30b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 15:51:28 -0400 Subject: [PATCH 21/58] fix badges --- client/src/components/Profile.jsx | 6 +++--- client/src/controllers/BadgesController.js | 1 - server/db/models/User.js | 7 +------ server/routes/user/Remember.js | 6 +++--- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/client/src/components/Profile.jsx b/client/src/components/Profile.jsx index 7c4d5c0..5d8dd5a 100644 --- a/client/src/components/Profile.jsx +++ b/client/src/components/Profile.jsx @@ -2,15 +2,15 @@ import { IconButton, Typography } from '@mui/material'; import Gravatar from 'react-gravatar'; import { Link } from 'react-router-dom'; -export default function Profile({ userData: { badges, email, level, points } }) { - +export default function Profile({ userData: { badgeCount, email, level, points } }) { + console.log(badgeCount, email, 123123) return ( <> - {level} - {points} + {level} – {points} {badgeCount?.gold}/{badgeCount?.silver}/{badgeCount?.bronze} ); diff --git a/client/src/controllers/BadgesController.js b/client/src/controllers/BadgesController.js index c8d1c09..4ab60f8 100644 --- a/client/src/controllers/BadgesController.js +++ b/client/src/controllers/BadgesController.js @@ -9,7 +9,6 @@ export default function BadgesController() { useEffect(() => { const loadBadges = async () => { const { badges: newBadges } = await getUserBadges(); - console.log(123123, newBadges) setBadges(newBadges); }; loadBadges(); diff --git a/server/db/models/User.js b/server/db/models/User.js index 9b06e22..a6b75e1 100644 --- a/server/db/models/User.js +++ b/server/db/models/User.js @@ -17,14 +17,9 @@ const User = mongoose.Schema( User.post('findOneAndUpdate', async (doc) => { const badges = calculateUserBadges(doc.points); - - console.log(`first: ${doc.badges}`); - doc.badges = [...new Set([...doc.badges, ...badges])]; - console.log(doc.badges); - - doc.save(); + await doc.save(); }); module.exports = mongoose.model('User', User); diff --git a/server/routes/user/Remember.js b/server/routes/user/Remember.js index 1d39ea4..8507fed 100644 --- a/server/routes/user/Remember.js +++ b/server/routes/user/Remember.js @@ -9,11 +9,11 @@ async function Remember(req, res) { const badgeCount = badges.reduce( (acc, badge) => { if (badge.rank === 'Gold') { - acc.gold = acc.gold++; + acc.gold = acc.gold + 1; } else if (badge.rank === 'Silver') { - acc.silver = acc.silver++; + acc.silver = acc.silver + 1; } else if (badge.rank === 'Bronze') { - acc.bronze = acc.bronze++; + acc.bronze = acc.bronze + 1; } return acc; From 1eb58e1865116c4744f2a64703e66b04ba034f20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 16:01:55 -0400 Subject: [PATCH 22/58] badge colors --- client/src/components/Profile.jsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/src/components/Profile.jsx b/client/src/components/Profile.jsx index 5d8dd5a..9adec95 100644 --- a/client/src/components/Profile.jsx +++ b/client/src/components/Profile.jsx @@ -9,8 +9,12 @@ export default function Profile({ userData: { badgeCount, email, level, points } - - {level} – {points} {badgeCount?.gold}/{badgeCount?.silver}/{badgeCount?.bronze} + + {level} + {points} + {badgeCount?.gold} + /{badgeCount?.silver} + /{badgeCount?.bronze} ); From 19e12e25e9e53a0465a6535cbe624ae88509752b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 16:03:22 -0400 Subject: [PATCH 23/58] remove print --- client/src/components/Profile.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/components/Profile.jsx b/client/src/components/Profile.jsx index 9adec95..9e724d7 100644 --- a/client/src/components/Profile.jsx +++ b/client/src/components/Profile.jsx @@ -3,7 +3,6 @@ import Gravatar from 'react-gravatar'; import { Link } from 'react-router-dom'; export default function Profile({ userData: { badgeCount, email, level, points } }) { - console.log(badgeCount, email, 123123) return ( <> From 79c356c134e998f56bb9f2dd7fcc19c0c425d041 Mon Sep 17 00:00:00 2001 From: anirudhsimhadri Date: Thu, 18 Aug 2022 16:04:18 -0400 Subject: [PATCH 24/58] frontend for read status of mails --- client/src/components/MailUnit.jsx | 5 +++-- server/routes/mail/Get.js | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/client/src/components/MailUnit.jsx b/client/src/components/MailUnit.jsx index d4c23c2..a694f7f 100644 --- a/client/src/components/MailUnit.jsx +++ b/client/src/components/MailUnit.jsx @@ -11,9 +11,8 @@ export default function MailUnit({ createdAt, subject, text, - read + read, }) { - return ( {subject} + + Read: {String(read)} diff --git a/server/routes/mail/Get.js b/server/routes/mail/Get.js index 81e0dc4..861c56f 100644 --- a/server/routes/mail/Get.js +++ b/server/routes/mail/Get.js @@ -7,15 +7,15 @@ async function Get(req, res) { // Fetch mail if expired if (Number(lastMailFetch) + config.mailExpires < Date.now()) { - const messages = await getAllMail({ username }); - return res.send({ messages }); - } else { + + await getAllMail({ username }) + } const messages = await Mail.find({ receiver: username }).sort({ createdAt: 'desc', }); return res.send({ messages }); - } + } module.exports = Get; From 0538d5b6f605e0766713ea41beeb2c05689b7354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 16:06:27 -0400 Subject: [PATCH 25/58] warning fix --- client/src/components/Profile.jsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/client/src/components/Profile.jsx b/client/src/components/Profile.jsx index 9e724d7..4b9c15d 100644 --- a/client/src/components/Profile.jsx +++ b/client/src/components/Profile.jsx @@ -8,13 +8,11 @@ export default function Profile({ userData: { badgeCount, email, level, points } - - {level} - {points} - {badgeCount?.gold} - /{badgeCount?.silver} - /{badgeCount?.bronze} - + {level} + {points} + {badgeCount?.gold} + /{badgeCount?.silver} + /{badgeCount?.bronze} ); } From cfb9e7a267d3827dd6f243780ff49fb90c89caf0 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 18 Aug 2022 16:27:00 -0400 Subject: [PATCH 26/58] Added new search by tags --- client/src/controllers/SearchBarController.js | 4 +- .../controllers/SearchResultsController.jsx | 37 +++++++--------- client/src/services/questionsServices.js | 15 +++---- server/routes/question/Search.js | 43 +++++++++++-------- 4 files changed, 51 insertions(+), 48 deletions(-) diff --git a/client/src/controllers/SearchBarController.js b/client/src/controllers/SearchBarController.js index 2202691..b01a111 100644 --- a/client/src/controllers/SearchBarController.js +++ b/client/src/controllers/SearchBarController.js @@ -15,12 +15,12 @@ export default function SearchBarController() { e.preventDefault(); if (location.pathname === '/questions/search') { - setSearchParams({ title: search, text: search, creator: search }, { replace: true }); + setSearchParams({ title: search }, { replace: true }); } else { navigate({ pathname: '/questions/search', search: `?${createSearchParams({ - title: search, text: search, creator: search + title: search, })}`, replace: true, }); diff --git a/client/src/controllers/SearchResultsController.jsx b/client/src/controllers/SearchResultsController.jsx index 196821f..85fe890 100644 --- a/client/src/controllers/SearchResultsController.jsx +++ b/client/src/controllers/SearchResultsController.jsx @@ -9,31 +9,26 @@ export default function SearchResultsController() { const [searchParams] = useSearchParams(); const [loading, setLoading] = useState(true); - const getData = async ({ question_id }) => { - let match = {}; - const regexMatch = {}; - const time = {}; - let newdate = searchParams.get('createdAt'); + const getData = async () => { + const creator = searchParams.get('creator'); + const title = searchParams.get('title'); + const text = searchParams.get('text'); + let tags = searchParams.get('tags'); + let time = searchParams.get('createdAt'); + let createdAt = {}; - if (newdate) { - newdate = new Date(newdate); - time['$gte'] = parseInt(newdate.getTime()); - newdate.setDate(newdate.getDate() + 1); - time['$lte'] = newdate.getTime(); - match = JSON.stringify({ createdAt: time }); + if (time) { + time = new Date(time); + createdAt['$gte'] = parseInt(time.getTime()); + time.setDate(time.getDate() + 1); + createdAt['$lte'] = time.getTime(); + } + if (tags) { + tags = tags.split(' '); } - const creator = searchParams.get('creator'); - if (creator) regexMatch.creator = creator; - - const text = searchParams.get('text'); - if (text) regexMatch.text = text.replaceAll(' ', '|'); - - const title = searchParams.get('title'); - if (title) regexMatch.title = title.replaceAll(' ', '|'); - setLoading(() => true); - const { questions } = await searchQuestions({ after: question_id, regexMatch, match }); + const { questions } = await searchQuestions({ creator, title, text, tags, createdAt }); setLoading(() => false); return questions; diff --git a/client/src/services/questionsServices.js b/client/src/services/questionsServices.js index 6420527..6f34d63 100644 --- a/client/src/services/questionsServices.js +++ b/client/src/services/questionsServices.js @@ -2,18 +2,17 @@ import { createEndpoint } from './api'; const callQuestionsAPI = createEndpoint('/questions'); -const searchQuestions = async (sort) => callQuestionsAPI('get', `/search`, sort); +const searchQuestions = async (sort) => { + console.log(sort); + return callQuestionsAPI('get', `/search`, sort); +}; 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 addBounty = async (question_id, data) => + callQuestionsAPI('patch', `/${question_id}/addBounty`, data); const updateQuestion = async (question_id, data) => callQuestionsAPI('patch', `/${question_id}`, data); @@ -110,5 +109,5 @@ export { closeQuestion, openQuestion, protectQuestion, - addBounty + addBounty, }; diff --git a/server/routes/question/Search.js b/server/routes/question/Search.js index 0d15e46..cce9659 100644 --- a/server/routes/question/Search.js +++ b/server/routes/question/Search.js @@ -3,27 +3,36 @@ const config = require('server/config.json'); const Question = require('server/db/models/Question'); async function Search(req, res) { - const { match, regexMatch, sort } = req.query; + const { creator, title, text, tags, createdAt } = req.query; - const questions = await Question.find({}); + console.log(req.query); - if (!success) return res.status(500).send(config.errorGeneric); + let searchQuery = {}; + if (creator) { + searchQuery['creator'] = { + $regex: creator, + }; + } + if (title) { + searchQuery['title'] = { + $regex: title, + }; + } + if (text) { + searchQuery['text'] = { + $regex: text, + }; + } + if (tags) { + searchQuery['tags'] = { $all: tags }; + } + if (createdAt) { + searchQuery['createdAt'] = createdAt; + } - // Patch search results to database - const questionSet = await Promise.all( - questions - .map((question) => ({ - id: question.question_id, - ...question, - })) - .map(async (question) => { - return Question.findByIdAndUpdate(question.id, question, { - upsert: true, - }); - }) - ); + const questions = await Question.find(searchQuery).sort({ createdAt: 'asc' }); - return res.send({ questions: questionSet.filter((question) => question) }); + return res.send({ questions }); } module.exports = Search; From c7bf08de299f4cc9e02fe5efb4cbf4f5cd96afac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 16:32:07 -0400 Subject: [PATCH 27/58] delete comment --- .../src/components/QAComponents/AnswerComment.jsx | 7 ++++++- client/src/components/QAComponents/Comment.jsx | 9 +++++++-- .../QAControllers/AnswerCommentController.jsx | 14 ++++++++++++-- .../QAControllers/CommentController.jsx | 15 +++++++++++++-- .../QAControllers/QuestionController.jsx | 14 +++++++------- 5 files changed, 45 insertions(+), 14 deletions(-) diff --git a/client/src/components/QAComponents/AnswerComment.jsx b/client/src/components/QAComponents/AnswerComment.jsx index 79fabdb..c89e9a7 100644 --- a/client/src/components/QAComponents/AnswerComment.jsx +++ b/client/src/components/QAComponents/AnswerComment.jsx @@ -1,4 +1,5 @@ -import { Divider, ListItem, ListItemText } from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { Divider, IconButton, ListItem, ListItemText } from '@mui/material'; import { CreationInfoTag } from 'controllers'; import { VoteControl } from 'controllers/QAControllers'; @@ -11,6 +12,7 @@ export default function AnswerComment({ text, upvotes, getVote, + onDelete, updateVote, }) { return ( @@ -21,6 +23,9 @@ export default function AnswerComment({ {text} + {onDelete && + + } diff --git a/client/src/components/QAComponents/Comment.jsx b/client/src/components/QAComponents/Comment.jsx index 6883072..8f715c8 100644 --- a/client/src/components/QAComponents/Comment.jsx +++ b/client/src/components/QAComponents/Comment.jsx @@ -1,4 +1,5 @@ -import { Divider, ListItem, ListItemText } from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { Divider, IconButton, ListItem, ListItemText } from '@mui/material'; import { CreationInfoTag } from 'controllers'; import { VoteControl } from 'controllers/QAControllers'; @@ -11,7 +12,8 @@ export default function Comment({ text, upvotes, getVote, - updateVote, + onDelete, + updateVote }) { return ( @@ -21,6 +23,9 @@ export default function Comment({ {text} + {onDelete && + + } diff --git a/client/src/controllers/QAControllers/AnswerCommentController.jsx b/client/src/controllers/QAControllers/AnswerCommentController.jsx index 3c9c4d6..b83c48a 100644 --- a/client/src/controllers/QAControllers/AnswerCommentController.jsx +++ b/client/src/controllers/QAControllers/AnswerCommentController.jsx @@ -1,9 +1,19 @@ +import { useUser } from 'contexts'; import { AnswerComment } from 'components/QAComponents'; -import { getAnswerCommentVote, updateAnswerCommentVote } from 'services/questionsServices'; +import { getAnswerCommentVote, deleteAnswerComment, updateAnswerCommentVote } from 'services/questionsServices'; export default function AnswerCommentController({ answer_id, comment_id, question_id, ...props }) { + const { userData } = useUser(); + const getVote = () => getAnswerCommentVote(question_id, answer_id, comment_id); const updateVote = (data) => updateAnswerCommentVote(question_id, answer_id, comment_id, data); + const onDelete = async () => { + if (userData?.username === props?.creator) { + const { status } = await deleteAnswerComment(question_id, answer_id, comment_id); + if (status === 200) + window.location.reload(false); + } + } - return ; + return ; } diff --git a/client/src/controllers/QAControllers/CommentController.jsx b/client/src/controllers/QAControllers/CommentController.jsx index 87047fe..0e22cc7 100644 --- a/client/src/controllers/QAControllers/CommentController.jsx +++ b/client/src/controllers/QAControllers/CommentController.jsx @@ -1,9 +1,20 @@ +import { useUser } from 'contexts'; import { Comment } from 'components/QAComponents'; -import { getCommentVote, updateCommentVote } from 'services/questionsServices'; +import { getCommentVote, deleteQuestionComment, updateCommentVote } from 'services/questionsServices'; + export default function CommentController({ comment_id, question_id, ...props }) { + const { userData } = useUser(); + const getVote = () => getCommentVote(question_id, comment_id); const updateVote = (data) => updateCommentVote(question_id, comment_id, data); + const onDelete = async () => { + if (userData?.username === props?.creator) { + const { status } = await deleteQuestionComment(question_id, comment_id); + if (status === 200) + window.location.reload(false); + } + } - return ; + return ; } diff --git a/client/src/controllers/QAControllers/QuestionController.jsx b/client/src/controllers/QAControllers/QuestionController.jsx index d182891..695ac14 100644 --- a/client/src/controllers/QAControllers/QuestionController.jsx +++ b/client/src/controllers/QAControllers/QuestionController.jsx @@ -37,8 +37,8 @@ export default function QuestionController() { useEffect(() => { const permissions = getPermissions(questionData, userData, ongoingVote); - if(userData.username)canBounty = (userData.level >= 4 && status !== 'closed' && !questionData.hasBounty && !questionData.hasAcceptedAnswer) - + if (userData.username) canBounty = (userData.level >= 4 && status !== 'closed' && !questionData.hasBounty && !questionData.hasAcceptedAnswer) + permissions.canBounty = canBounty; setPermissions(permissions); setVote(); @@ -77,14 +77,14 @@ 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 } + 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{ + } else { setShow(true) } } @@ -111,7 +111,7 @@ export default function QuestionController() { postComment, show, handleBounty - + }} /> )} From 2479611245ce508fe454231788a9076de24a47bb Mon Sep 17 00:00:00 2001 From: tejusk2 <92613406+tejusk2@users.noreply.github.com> Date: Thu, 18 Aug 2022 15:35:13 -0500 Subject: [PATCH 28/58] edit vote beginning --- client/package.json | 1 + .../src/components/QAComponents/Suggest.jsx | 67 +++++++++++++++++++ client/src/containers/QA.jsx | 4 +- .../EditQuestionFormController.js | 28 ++++++++ .../FormControllers/FormController.js | 10 ++- client/src/services/fields.js | 17 +++++ client/src/services/levels.json | 11 +-- client/src/services/questionsServices.js | 8 ++- client/src/services/schemas.js | 6 ++ package-lock.json | 2 + server/db/models/Question.js | 2 +- server/routes/question/EditQuestionBody.js | 12 ++-- 12 files changed, 153 insertions(+), 15 deletions(-) create mode 100644 client/src/components/QAComponents/Suggest.jsx create mode 100644 client/src/controllers/FormControllers/EditQuestionFormController.js diff --git a/client/package.json b/client/package.json index 289fe82..6fe2359 100644 --- a/client/package.json +++ b/client/package.json @@ -16,6 +16,7 @@ "js-cookie": "^3.0.1", "marked": "^4.0.18", "react": "^18.2.0", + "react-copy-to-clipboard": "^5.1.0", "react-gravatar": "^2.6.3", "react-helmet": "^6.1.0", "react-markdown": "^8.0.3", diff --git a/client/src/components/QAComponents/Suggest.jsx b/client/src/components/QAComponents/Suggest.jsx new file mode 100644 index 0000000..48e4793 --- /dev/null +++ b/client/src/components/QAComponents/Suggest.jsx @@ -0,0 +1,67 @@ +import {Tooltip, Button, Card, CardContent, Typography} from '@mui/material' +import { editQuestion } from 'services/questionsServices'; +import EditFormController from "controllers/FormControllers/EditQuestionFormController"; +import { useEffect, useState } from "react"; +import { useUser, useQuestion } from 'contexts'; +import { Markdown } from 'components'; +export default function Suggest(){ + const [show, setShow] = useState(false); + const [canSuggest, setCanSuggest] = useState(false) + const {userData} = useUser(); + const {questionData} = useQuestion(); + const [ongoingEdit, setOngoingEdit] = useState({users : [], new : []}) + + useEffect(()=>{ + setCanSuggest(userData.level >= 7) + + if(ongoingEdit.users.length > 0)setCanSuggest((userData.username !== ongoingEdit.users[0])) + if(!questionData.loading) setOngoingEdit({users : questionData.edit, new : questionData.editText}) + },[userData, questionData, setCanSuggest]) + + async function toggleShow(){ + if(questionData.edit.length === 0){ + setShow(!show) + }else{ + const { status } = await editQuestion(questionData.question_id); + } + + } + + + + return ( +
+ + + + + + {show && ( + + + + + + )} + {ongoingEdit.users.length > 0 && + + + + + + {ongoingEdit.users} have voted to edit the question + + + } + + +
+ ); +} \ No newline at end of file diff --git a/client/src/containers/QA.jsx b/client/src/containers/QA.jsx index 757301f..64cd8b7 100644 --- a/client/src/containers/QA.jsx +++ b/client/src/containers/QA.jsx @@ -1,4 +1,5 @@ import { List, ListItem } from '@mui/material'; +import Suggest from 'components/QAComponents/Suggest'; import { AnswersList, CommentsList, CreateAnswer, Question } from 'controllers/QAControllers'; export default function QA() { @@ -8,7 +9,8 @@ export default function QA() { - + +
diff --git a/client/src/controllers/FormControllers/EditQuestionFormController.js b/client/src/controllers/FormControllers/EditQuestionFormController.js new file mode 100644 index 0000000..ca29d64 --- /dev/null +++ b/client/src/controllers/FormControllers/EditQuestionFormController.js @@ -0,0 +1,28 @@ + +import { Form } from 'controllers/FormControllers'; +import { generateEditFields } from 'services/fields'; +import { editQuestion } from 'services/questionsServices'; +import { questionSchema } from 'services/schemas'; +import { useQuestion } from 'contexts'; + +export default function EditFormController() { + const {questionData} = useQuestion(); + const {title, text} = questionData; + + + + const editTheQuestion = async (fields) => { + console.log("hello") + const { status } = await editQuestion(questionData.question_id, fields); + window.location.reload(false); + }; + + const field = generateEditFields + + return !questionData.loading && Form({ + fields: field, + onSubmit: editTheQuestion, + validationSchema: questionSchema, + initialValues: {etext : text, etitle : title} + }); +} diff --git a/client/src/controllers/FormControllers/FormController.js b/client/src/controllers/FormControllers/FormController.js index 9d2dcc3..e033785 100644 --- a/client/src/controllers/FormControllers/FormController.js +++ b/client/src/controllers/FormControllers/FormController.js @@ -1,6 +1,6 @@ import { useFormik } from 'formik'; import { useEffect } from 'react'; - +import { useQuestion } from 'contexts'; import { Form } from 'components'; import { useForm } from 'contexts'; @@ -10,11 +10,15 @@ export default function FormController({ validate, validationSchema, children, + initialValues }) { - const { setContent } = useForm(); + + const { setContent } = useForm(); + useEffect(() => { setContent(''); + }, [setContent]); const handleChange = (e) => { @@ -22,7 +26,7 @@ export default function FormController({ }; const formik = useFormik({ - initialValues: fields.reduce((acc, { id }) => ({ ...acc, [id]: '' }), {}), + initialValues: fields.reduce((acc, { id }) => ({ ...acc, [id]: initialValues[id] || '' }), {}), onSubmit, validate, validateOnChange: false, diff --git a/client/src/services/fields.js b/client/src/services/fields.js index 5a8295e..f5db0c2 100644 --- a/client/src/services/fields.js +++ b/client/src/services/fields.js @@ -129,6 +129,22 @@ const searchFields = [ }, ]; +const generateEditFields = + + [ + { + id: 'etitle', + label: 'Title', + + }, + { + id: 'etext', + label: 'Text', + multiline: true, + + }, + ] + export { answerFields, askQuestionFields, @@ -140,4 +156,5 @@ export { recoverFields, resetFields, searchFields, + generateEditFields }; diff --git a/client/src/services/levels.json b/client/src/services/levels.json index 67b2b4f..009c7d5 100644 --- a/client/src/services/levels.json +++ b/client/src/services/levels.json @@ -1,8 +1,11 @@ { + "Level 1": 1, "Level 2": 15, "Level 3": 50, - "Level 4": 125, - "Level 5": 1000, - "Level 6": 3000, - "Level 7": 10000 + "Level 4": 75, + "Level 5": 125, + "Level 6": 1000, + "Level 7": 2000, + "Level 8" : 3000, + "Level 9" : 10000 } diff --git a/client/src/services/questionsServices.js b/client/src/services/questionsServices.js index 6420527..938ea83 100644 --- a/client/src/services/questionsServices.js +++ b/client/src/services/questionsServices.js @@ -14,6 +14,11 @@ const addBounty = async (question_id,data) => callQuestionsAPI( data ) +const editQuestion = async (question_id,data) => callQuestionsAPI( + 'patch', + `/${question_id}/edit`, + data +) const updateQuestion = async (question_id, data) => callQuestionsAPI('patch', `/${question_id}`, data); @@ -110,5 +115,6 @@ export { closeQuestion, openQuestion, protectQuestion, - addBounty + addBounty, + editQuestion }; diff --git a/client/src/services/schemas.js b/client/src/services/schemas.js index f232e96..9f35a61 100644 --- a/client/src/services/schemas.js +++ b/client/src/services/schemas.js @@ -71,6 +71,12 @@ const questionSchema = Yup.object({ text: Yup.string() .max(3000, 'Body cannot be longer than 3000 characters') .required('A body is required'), + etitle: Yup.string() + .max(150, 'Title cannot be longer then 150 characters.') + .required('A title is required.'), + etext: Yup.string() + .max(3000, 'Body cannot be longer than 3000 characters') + .required('A body is required'), }); const patchSchema = Yup.object().shape( diff --git a/package-lock.json b/package-lock.json index 60df64f..122433f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "js-cookie": "^3.0.1", "marked": "^4.0.18", "react": "^18.2.0", + "react-copy-to-clipboard": "^5.1.0", "react-gravatar": "^2.6.3", "react-helmet": "^6.1.0", "react-markdown": "^8.0.3", @@ -32177,6 +32178,7 @@ "js-cookie": "^3.0.1", "marked": "^4.0.18", "react": "^18.2.0", + "react-copy-to-clipboard": "*", "react-gravatar": "^2.6.3", "react-helmet": "^6.1.0", "react-markdown": "^8.0.3", diff --git a/server/db/models/Question.js b/server/db/models/Question.js index 5a6c3aa..b3f8a16 100644 --- a/server/db/models/Question.js +++ b/server/db/models/Question.js @@ -14,7 +14,7 @@ const Question = mongoose.Schema( reopen: [String], close: [String], edit: [String], - editText: { type: String }, + editText: [String], tags: [String], question_id: { type: String, required: true }, status: { type: String, required: true, default: 'open' }, diff --git a/server/routes/question/EditQuestionBody.js b/server/routes/question/EditQuestionBody.js index 04dd9ac..7a06cea 100644 --- a/server/routes/question/EditQuestionBody.js +++ b/server/routes/question/EditQuestionBody.js @@ -7,7 +7,7 @@ const getUserLevel = require('server/utils/getUserLevel'); async function EditQuestionStatusClosed(req, res) { const { user } = req; const { question_id } = req.params; - const { text } = req.body; + const { etext, etitle } = req.body; // Verify user has required level if (getUserLevel(user.points) < 7) { @@ -20,12 +20,12 @@ async function EditQuestionStatusClosed(req, res) { // Toggle question vote if (question.edit.length === 0) { - if (!text) { + if (!etext || !etitle) { return res.status(400).send(config.errorIncomplete); } await Question.findByIdAndUpdate(question_id, { - editText: text, + editText: etext, $push: { edit: user.username }, }); @@ -48,11 +48,13 @@ async function EditQuestionStatusClosed(req, res) { if (question.edit.length === 2) { const question = await Question.findByIdAndUpdate(question_id, { edit: [], - text, + etext, + etitle }); const { success } = await createRequest('patch', `/questions/${question_id}`, { - text, + etext, + etitle }); return success From 40e072c99f6f29c95818fe16fd941ea6e1b99d0a Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 18 Aug 2022 16:46:06 -0400 Subject: [PATCH 29/58] Added tag to forms --- client/src/services/fields.js | 2 ++ client/src/services/schemas.js | 8 ++++++++ server/routes/question/CreateQuestion.js | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/client/src/services/fields.js b/client/src/services/fields.js index 5a8295e..4545a94 100644 --- a/client/src/services/fields.js +++ b/client/src/services/fields.js @@ -22,6 +22,7 @@ const askQuestionFields = [ label: 'Text', multiline: true, }, + { id: 'tags', label: 'Tags' }, ]; const commentFields = [ @@ -127,6 +128,7 @@ const searchFields = [ id: 'creator', label: 'Creator', }, + { id: 'tags', label: 'Tags (separated by spaces)' }, ]; export { diff --git a/client/src/services/schemas.js b/client/src/services/schemas.js index f232e96..6261c84 100644 --- a/client/src/services/schemas.js +++ b/client/src/services/schemas.js @@ -21,6 +21,10 @@ const searchSchema = Yup.object({ text: Yup.string().max(300, 'Text must be at most 300 characters, try and use keywords only '), // createdAt: Yup.string().max(11, 'Date must be at most 11 characters, make sure body is formatted correctly'), creator: Yup.string().max(16, 'username must be at most 16 characters'), + tags: Yup.string().matches( + /^(?:\b\w+\b[\s\r\n]*){0,5}$/, + 'Tags should be separated by spaces and are limited to 5' + ), }); const loginSchema = Yup.object({ @@ -71,6 +75,10 @@ const questionSchema = Yup.object({ text: Yup.string() .max(3000, 'Body cannot be longer than 3000 characters') .required('A body is required'), + tags: Yup.string().matches( + /^(?:\b\w+\b[\s\r\n]*){0,5}$/, + 'Tags should be separated by spaces and are limited to 5' + ), }); const patchSchema = Yup.object().shape( diff --git a/server/routes/question/CreateQuestion.js b/server/routes/question/CreateQuestion.js index 0aff3e2..af9ab0f 100644 --- a/server/routes/question/CreateQuestion.js +++ b/server/routes/question/CreateQuestion.js @@ -9,7 +9,7 @@ async function CreateQuestion(req, res) { if (!title || !text) return res.status(400).send(config.errorIncomplete); - if (tags && tags.length > 5) { + if (tags && tags.split(' ').length > 5) { return res.status(400).send(config.errorIncomplete); } From 426e68f44633ed0c2f4469e3037c7f38ca92e1e1 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 18 Aug 2022 16:47:35 -0400 Subject: [PATCH 30/58] Removed logs --- server/db/models/User.js | 4 ---- server/routes/question/Search.js | 2 -- 2 files changed, 6 deletions(-) diff --git a/server/db/models/User.js b/server/db/models/User.js index 9b06e22..c959d32 100644 --- a/server/db/models/User.js +++ b/server/db/models/User.js @@ -18,12 +18,8 @@ const User = mongoose.Schema( User.post('findOneAndUpdate', async (doc) => { const badges = calculateUserBadges(doc.points); - console.log(`first: ${doc.badges}`); - doc.badges = [...new Set([...doc.badges, ...badges])]; - console.log(doc.badges); - doc.save(); }); diff --git a/server/routes/question/Search.js b/server/routes/question/Search.js index cce9659..058f3df 100644 --- a/server/routes/question/Search.js +++ b/server/routes/question/Search.js @@ -5,8 +5,6 @@ const Question = require('server/db/models/Question'); async function Search(req, res) { const { creator, title, text, tags, createdAt } = req.query; - console.log(req.query); - let searchQuery = {}; if (creator) { searchQuery['creator'] = { From 77d5ff9a01f27a05415d16ec00a2d8756ddaa6db Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 18 Aug 2022 16:54:23 -0400 Subject: [PATCH 31/58] Fixed create question --- server/routes/question/CreateQuestion.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server/routes/question/CreateQuestion.js b/server/routes/question/CreateQuestion.js index af9ab0f..decd845 100644 --- a/server/routes/question/CreateQuestion.js +++ b/server/routes/question/CreateQuestion.js @@ -21,7 +21,11 @@ async function CreateQuestion(req, res) { if (!success) return res.status(500).send(config.errorGeneric); - const newQuestion = await Question.create({ ...question, tags, _id: question.question_id }); + const newQuestion = await Question.create({ + ...question, + tags: tags.split(' '), + _id: question.question_id, + }); // Increment user points by 1 await createRequest('patch', `/users/${user.username}/points`, { From 63da1e7ceec2e61b67b63bbe69cf5b068c7c7d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 16:56:11 -0400 Subject: [PATCH 32/58] mobile navbar userdata --- client/src/components/MobileNavbar.jsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/client/src/components/MobileNavbar.jsx b/client/src/components/MobileNavbar.jsx index 8e9be9a..01afb2f 100644 --- a/client/src/components/MobileNavbar.jsx +++ b/client/src/components/MobileNavbar.jsx @@ -40,6 +40,15 @@ export default function MobileNavbar({ logout, userData, open, setOpen, mode, se Dashboard + + + {userData?.level} + {userData?.points} + {userData?.badgeCount?.gold} + /{userData?.badgeCount?.silver} + /{userData?.badgeCount?.bronze} + + From 4a24755a7f02a0bf94b22f77ecf173bd82686c5a Mon Sep 17 00:00:00 2001 From: tejusk2 <92613406+tejusk2@users.noreply.github.com> Date: Thu, 18 Aug 2022 15:58:35 -0500 Subject: [PATCH 33/58] finished edit --- client/src/components/QAComponents/CreateAnswer.jsx | 2 +- client/src/components/QAComponents/Suggest.jsx | 13 +++++++------ .../FormControllers/EditQuestionFormController.js | 4 ++-- client/src/services/schemas.js | 6 +++++- server/routes/question/EditQuestionBody.js | 11 ++++++----- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/client/src/components/QAComponents/CreateAnswer.jsx b/client/src/components/QAComponents/CreateAnswer.jsx index fe8dc03..8479ff6 100644 --- a/client/src/components/QAComponents/CreateAnswer.jsx +++ b/client/src/components/QAComponents/CreateAnswer.jsx @@ -5,7 +5,7 @@ import { AnswerForm } from 'controllers/FormControllers'; export default function CreateAnswer({ canAnswer, show, toggleShow }) { return ( -
+
{ setCanSuggest(userData.level >= 7) - if(ongoingEdit.users.length > 0)setCanSuggest((userData.username !== ongoingEdit.users[0])) + if(!questionData.loading) setOngoingEdit({users : questionData.edit, new : questionData.editText}) },[userData, questionData, setCanSuggest]) async function toggleShow(){ - if(questionData.edit.length === 0){ + if(questionData.edit.length === 0 || userData.username !== ongoingEdit.users[0]){ setShow(!show) }else{ const { status } = await editQuestion(questionData.question_id); + window.location.reload(false); } } @@ -30,7 +31,7 @@ export default function Suggest(){ return ( -
+
0 && - - + New Title: + New Text: - {ongoingEdit.users} have voted to edit the question + {ongoingEdit.users} has voted to edit the question } diff --git a/client/src/controllers/FormControllers/EditQuestionFormController.js b/client/src/controllers/FormControllers/EditQuestionFormController.js index ca29d64..aec0831 100644 --- a/client/src/controllers/FormControllers/EditQuestionFormController.js +++ b/client/src/controllers/FormControllers/EditQuestionFormController.js @@ -2,7 +2,7 @@ import { Form } from 'controllers/FormControllers'; import { generateEditFields } from 'services/fields'; import { editQuestion } from 'services/questionsServices'; -import { questionSchema } from 'services/schemas'; +import { editSchema } from 'services/schemas'; import { useQuestion } from 'contexts'; export default function EditFormController() { @@ -22,7 +22,7 @@ export default function EditFormController() { return !questionData.loading && Form({ fields: field, onSubmit: editTheQuestion, - validationSchema: questionSchema, + validationSchema: editSchema, initialValues: {etext : text, etitle : title} }); } diff --git a/client/src/services/schemas.js b/client/src/services/schemas.js index 9f35a61..0e81f86 100644 --- a/client/src/services/schemas.js +++ b/client/src/services/schemas.js @@ -71,13 +71,16 @@ const questionSchema = Yup.object({ text: Yup.string() .max(3000, 'Body cannot be longer than 3000 characters') .required('A body is required'), +}); + +const editSchema = Yup.object({ etitle: Yup.string() .max(150, 'Title cannot be longer then 150 characters.') .required('A title is required.'), etext: Yup.string() .max(3000, 'Body cannot be longer than 3000 characters') .required('A body is required'), -}); +}) const patchSchema = Yup.object().shape( { @@ -117,4 +120,5 @@ export { registerSchema, resetSchema, searchSchema, + editSchema }; diff --git a/server/routes/question/EditQuestionBody.js b/server/routes/question/EditQuestionBody.js index 7a06cea..785df46 100644 --- a/server/routes/question/EditQuestionBody.js +++ b/server/routes/question/EditQuestionBody.js @@ -8,7 +8,7 @@ async function EditQuestionStatusClosed(req, res) { const { user } = req; const { question_id } = req.params; const { etext, etitle } = req.body; - + let eObj = [etext, etitle] // Verify user has required level if (getUserLevel(user.points) < 7) { return res.status(403).send(config.errorForbidden); @@ -25,7 +25,7 @@ async function EditQuestionStatusClosed(req, res) { } await Question.findByIdAndUpdate(question_id, { - editText: etext, + editText: eObj, $push: { edit: user.username }, }); @@ -48,13 +48,14 @@ async function EditQuestionStatusClosed(req, res) { if (question.edit.length === 2) { const question = await Question.findByIdAndUpdate(question_id, { edit: [], - etext, - etitle + eObj, + }); const { success } = await createRequest('patch', `/questions/${question_id}`, { etext, - etitle + eObj + }); return success From 9d6903a183f04267046a2660ace88d010b14d9b5 Mon Sep 17 00:00:00 2001 From: Ani Simhadri Date: Thu, 18 Aug 2022 16:58:42 -0400 Subject: [PATCH 34/58] config --- package-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 122433f..f03b82c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32178,7 +32178,7 @@ "js-cookie": "^3.0.1", "marked": "^4.0.18", "react": "^18.2.0", - "react-copy-to-clipboard": "*", + "react-copy-to-clipboard": "^5.1.0", "react-gravatar": "^2.6.3", "react-helmet": "^6.1.0", "react-markdown": "^8.0.3", From 0350ef8c0895bd90ea0a2b7befeb55162da03006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 17:02:02 -0400 Subject: [PATCH 35/58] fix search initalvalues --- client/src/components/ListQuestion.jsx | 6 ++++-- client/src/components/PaginatedList.jsx | 2 +- client/src/controllers/FormControllers/FormController.js | 8 ++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/client/src/components/ListQuestion.jsx b/client/src/components/ListQuestion.jsx index 9fbf637..3ae30a0 100644 --- a/client/src/components/ListQuestion.jsx +++ b/client/src/components/ListQuestion.jsx @@ -11,6 +11,7 @@ export default function ListQuestion({ creator, downvotes, status, + tags, text, title, upvotes, @@ -19,17 +20,18 @@ export default function ListQuestion({ }) { const sm = useMediaQuery((theme) => theme.breakpoints.only('sm')); const md = useMediaQuery((theme) => theme.breakpoints.only('md')); + console.log(123123, tags) return ( - {sm || md ? : null} + {sm || md ? : null} - {sm || md ? null: } + {sm || md ? null : } ))} {count > 1 && ( - + { setContent(''); - + }, [setContent]); const handleChange = (e) => { From 016f6b3ed2592fa43a80a2dc69e68bbbf53a0922 Mon Sep 17 00:00:00 2001 From: tejusk2 <92613406+tejusk2@users.noreply.github.com> Date: Thu, 18 Aug 2022 16:02:09 -0500 Subject: [PATCH 36/58] schema fix --- client/src/services/schemas.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/src/services/schemas.js b/client/src/services/schemas.js index d2be36a..9abbee0 100644 --- a/client/src/services/schemas.js +++ b/client/src/services/schemas.js @@ -107,6 +107,14 @@ const patchSchema = Yup.object().shape( ['password', 'password'], ] ); +const editSchema = Yup.object({ + etitle: Yup.string() + .max(150, 'Title cannot be longer then 150 characters.') + .required('A title is required.'), + etext: Yup.string() + .max(3000, 'Body cannot be longer than 3000 characters') + .required('A body is required') +}); export { answerSchema, From 57e52c9a14b83253d23775580e287d9ce7f7e551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 17:12:00 -0400 Subject: [PATCH 37/58] accepted answer chip --- client/src/components/ListQuestion.jsx | 6 ++-- client/src/components/ListQuestionInfo.jsx | 33 ++++++++++++++++------ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/client/src/components/ListQuestion.jsx b/client/src/components/ListQuestion.jsx index 3ae30a0..05dda16 100644 --- a/client/src/components/ListQuestion.jsx +++ b/client/src/components/ListQuestion.jsx @@ -20,7 +20,7 @@ export default function ListQuestion({ }) { const sm = useMediaQuery((theme) => theme.breakpoints.only('sm')); const md = useMediaQuery((theme) => theme.breakpoints.only('md')); - console.log(123123, tags) + return ( @@ -31,7 +31,7 @@ export default function ListQuestion({ - {sm || md ? null : } + {sm || md ? null : } {text.replace(/<[^>]*>?/gm, '')} - Accepted: {String(hasAcceptedAnswer)} - diff --git a/client/src/components/ListQuestionInfo.jsx b/client/src/components/ListQuestionInfo.jsx index 57c1a87..e61cc4e 100644 --- a/client/src/components/ListQuestionInfo.jsx +++ b/client/src/components/ListQuestionInfo.jsx @@ -1,16 +1,31 @@ -import { Typography } from "@mui/material" -export default function ListQuestionInfo({upvotes, downvotes, answers, views, inline}){ - return inline? ( +import { Chip, Typography } from '@mui/material'; +export default function ListQuestionInfo({ upvotes, downvotes, answers, hasAcceptedAnswer, views, inline }) { + return inline ? ( <> - {upvotes - downvotes} votes | - {answers} answers | - {views} views + {upvotes - downvotes} votes | + {answers} answers | + {views} views + + Accepted: + + ) : ( <> - {upvotes - downvotes} votes - {answers} answers - {views} views + {upvotes - downvotes} votes + {answers} answers + {views} views + + Accepted: + ) } \ No newline at end of file From c9680903cbfed1dc4e89d3d675d48c1549fa581d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 17:17:00 -0400 Subject: [PATCH 38/58] listquestion tags --- client/src/components/ListQuestion.jsx | 4 ++-- client/src/components/ListQuestionInfo.jsx | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client/src/components/ListQuestion.jsx b/client/src/components/ListQuestion.jsx index 05dda16..22a7008 100644 --- a/client/src/components/ListQuestion.jsx +++ b/client/src/components/ListQuestion.jsx @@ -27,11 +27,11 @@ export default function ListQuestion({ - {sm || md ? : null} + {sm || md ? : null} - {sm || md ? null : } + {sm || md ? null : } {upvotes - downvotes} votes | @@ -13,6 +13,7 @@ export default function ListQuestionInfo({ upvotes, downvotes, answers, hasAccep label={hasAcceptedAnswer ? 'yes' : 'no'} size='small' /> + Tags: {tags.join(', ')} ) : ( <> @@ -26,6 +27,7 @@ export default function ListQuestionInfo({ upvotes, downvotes, answers, hasAccep size='small' /> + Tags: {tags.join(', ')} ) } \ No newline at end of file From cc2021767743d08d918e07b195a04c861479f390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 17:20:37 -0400 Subject: [PATCH 39/58] qa tags --- .../src/components/QAComponents/Question.jsx | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/client/src/components/QAComponents/Question.jsx b/client/src/components/QAComponents/Question.jsx index 259b782..386eafe 100644 --- a/client/src/components/QAComponents/Question.jsx +++ b/client/src/components/QAComponents/Question.jsx @@ -39,6 +39,7 @@ export default function Question({ downvotes, hasAcceptedAnswer, status, + tags, title, text, upvotes, @@ -62,11 +63,11 @@ export default function Question({ const [value, setValue] = useState(75); canBounty = canBounty && !hasBounty - function handleBountyFix(){ + function handleBountyFix() { handleBounty(value) } return ( - + <> {title} @@ -92,6 +93,8 @@ export default function Question({ label={hasAcceptedAnswer ? 'yes' : 'no'} size='small' /> + Tags: {tags.join(', ')} +
- { show && - { - if (e.target.value === "") { - setValue(75); - return; + {show && + { + 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); + } + + + }} /> } - const value = e.target.value; - if (value > max) { - setValue(max); - } else if (value < min) { - setValue(min); - } else { - setValue(value); - } - - - }}/> - }
@@ -177,7 +180,7 @@ export default function Question({ {ongoingVote.users.toString()} - voting to {ongoingVote.type} this question{' '} )} - {hasBounty && ( + {hasBounty && ( there is a {hasBounty} point bounty on this question From 45e21f21a27496ac2acdd912d6852c4b41474b21 Mon Sep 17 00:00:00 2001 From: C4 Patino Date: Thu, 18 Aug 2022 17:20:54 -0400 Subject: [PATCH 40/58] Fixed buffet sections --- client/src/containers/Buffet.jsx | 25 +++++++++---------------- server/routes/question/Search.js | 27 +++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/client/src/containers/Buffet.jsx b/client/src/containers/Buffet.jsx index e6e5a4b..4eeebb1 100644 --- a/client/src/containers/Buffet.jsx +++ b/client/src/containers/Buffet.jsx @@ -6,30 +6,23 @@ import { ListQuestion, LoadingBar } from 'components'; import { PaginatedList } from 'controllers'; import { searchQuestions } from 'services/questionsServices'; -const recent = {}; -const best = { sort: 'u' }; -const interesting = { match: JSON.stringify({ answers: 0 }), sort: 'uvc' }; -const hot = { match: JSON.stringify({ hasAcceptedAnswer: false }), sort: 'uvac' }; -const sortObjArr = [recent, best, interesting, hot]; - export default function Buffet() { - const [sort, setSort] = useState(0); + const [sort, setSort] = useState(''); const [loading, setLoading] = useState(true); const [, setSearchParams] = useSearchParams(); const getData = async ({ question_id }) => { const { questions } = await searchQuestions({ - ...sortObjArr[sort], - after: question_id, + sort, }); setLoading(() => false); return questions; }; - const handleSortChange = (_, newSort) => { - setSearchParams(sortObjArr[newSort]); - setSort(newSort); + const handleSortChange = (_, value) => { + setSearchParams({ sort: value }); + setSort(value); }; return ( @@ -60,10 +53,10 @@ export default function Buffet() { onChange={handleSortChange} style={{ display: 'block', marginTop: '1%' }} > - Recent - Best - Interesting - Hot + Recent + Best + Interesting + Hot {loading && } diff --git a/server/routes/question/Search.js b/server/routes/question/Search.js index 058f3df..eb5311a 100644 --- a/server/routes/question/Search.js +++ b/server/routes/question/Search.js @@ -3,7 +3,7 @@ const config = require('server/config.json'); const Question = require('server/db/models/Question'); async function Search(req, res) { - const { creator, title, text, tags, createdAt } = req.query; + const { creator, title, text, tags, createdAt, sort } = req.query; let searchQuery = {}; if (creator) { @@ -28,7 +28,30 @@ async function Search(req, res) { searchQuery['createdAt'] = createdAt; } - const questions = await Question.find(searchQuery).sort({ createdAt: 'asc' }); + let sortQuery = [[createdAt, 'desc']]; + switch (sort) { + case 'u': + sortQuery = [['upvotes', 'desc']]; + break; + case 'uvc': + sortQuery = [ + ['upvotes', 'desc'], + ['view', 'desc'], + ['comments', 'desc'], + ]; + break; + case 'uvac': + sortQuery = [ + ['upvotes', 'desc'], + ['view', 'desc'], + ['answers', 'desc'], + ['comments', 'desc'], + ]; + searchQuery['hasAcceptedAnswer'] = false; + break; + } + + const questions = await Question.find(searchQuery).sort(sortQuery); return res.send({ questions }); } From a61510ed8c2a52aaa248d587ea0b1a32d76ad736 Mon Sep 17 00:00:00 2001 From: Ani Simhadri Date: Thu, 18 Aug 2022 17:23:27 -0400 Subject: [PATCH 41/58] config --- client/src/components/Badges.jsx | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/client/src/components/Badges.jsx b/client/src/components/Badges.jsx index c63f077..91aae1b 100644 --- a/client/src/components/Badges.jsx +++ b/client/src/components/Badges.jsx @@ -1,5 +1,6 @@ import WorkspacePremiumIcon from '@mui/icons-material/WorkspacePremium'; import { Grid, Tooltip, Typography } from '@mui/material'; +import { Stack } from '@material-ui/core'; function Badge({ title = 'Good Question', @@ -31,16 +32,20 @@ export default function Badges({ badges: { obtained, unobtained } }) { return ( <> - - Obtained Badges - - {badgesGrid(obtained)} + + + Obtained Badges + + {badgesGrid(obtained)} + - - Unobtained Badges - - {badgesGrid(unobtained)} + + + Unobtained Badges + + {badgesGrid(unobtained)} + ); From e742c0de50a576b7aef81295df6b44d6d875ed25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cspicecat=E2=80=9D?= Date: Thu, 18 Aug 2022 17:24:38 -0400 Subject: [PATCH 42/58] improve dashboard badges display --- client/src/components/Badges.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/Badges.jsx b/client/src/components/Badges.jsx index c63f077..b602bed 100644 --- a/client/src/components/Badges.jsx +++ b/client/src/components/Badges.jsx @@ -31,13 +31,13 @@ export default function Badges({ badges: { obtained, unobtained } }) { return ( <> - + Obtained Badges {badgesGrid(obtained)} - + Unobtained Badges {badgesGrid(unobtained)} From bb6452b15d57323ae8dd4647f730db8335860724 Mon Sep 17 00:00:00 2001 From: tejusk2 <92613406+tejusk2@users.noreply.github.com> Date: Thu, 18 Aug 2022 16:27:50 -0500 Subject: [PATCH 43/58] level tooltips changed --- client/src/components/QAComponents/CommentControl.jsx | 2 +- client/src/components/QAComponents/CreateAnswer.jsx | 2 +- client/src/components/QAComponents/Question.jsx | 4 ++-- client/src/components/QAComponents/VoteControl.jsx | 2 +- .../controllers/QAControllers/VoteControlController.js | 2 +- client/src/services/getPermissions.js | 8 ++++---- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client/src/components/QAComponents/CommentControl.jsx b/client/src/components/QAComponents/CommentControl.jsx index d8eaa8f..76f1491 100644 --- a/client/src/components/QAComponents/CommentControl.jsx +++ b/client/src/components/QAComponents/CommentControl.jsx @@ -18,7 +18,7 @@ export default function CommentControl({ title={ canComment ? '' - : 'You must be level 3 to comment or level 5 if this question is protected' + : 'You must be level 3 to comment or level 6 if this question is protected' } > diff --git a/client/src/components/QAComponents/CreateAnswer.jsx b/client/src/components/QAComponents/CreateAnswer.jsx index 8479ff6..5354679 100644 --- a/client/src/components/QAComponents/CreateAnswer.jsx +++ b/client/src/components/QAComponents/CreateAnswer.jsx @@ -10,7 +10,7 @@ export default function CreateAnswer({ canAnswer, show, toggleShow }) { title={ canAnswer ? '' - : 'You need to be authenticated to answer or level 5 if the question is protected' + : 'You need to be authenticated to answer or level 6 if the question is protected' } > diff --git a/client/src/components/QAComponents/Question.jsx b/client/src/components/QAComponents/Question.jsx index 259b782..217ab15 100644 --- a/client/src/components/QAComponents/Question.jsx +++ b/client/src/components/QAComponents/Question.jsx @@ -103,7 +103,7 @@ export default function Question({ Ask question - +