From 35c231b88c442ac509bdd8a084c0813afd5c20b1 Mon Sep 17 00:00:00 2001 From: Sawan Date: Fri, 8 Nov 2024 22:19:41 +0530 Subject: [PATCH] Added backend api for both answered and unanswered question --- backend/app.js | 2 + backend/controllers/discussionController.js | 49 ++++++ backend/models/discussion.js | 21 +++ backend/routes/discussionRoutes.js | 12 ++ frontend/src/pages/DiscussionForum.js | 155 +++++++++---------- frontend/src/styles/output.css | 160 +++++++------------- 6 files changed, 208 insertions(+), 191 deletions(-) create mode 100644 backend/controllers/discussionController.js create mode 100644 backend/models/discussion.js create mode 100644 backend/routes/discussionRoutes.js diff --git a/backend/app.js b/backend/app.js index 7062278..4f42f81 100644 --- a/backend/app.js +++ b/backend/app.js @@ -9,6 +9,7 @@ import ratingRoutes from "./routes/ratingRoutes.js"; import getInTouch from "./routes/getInTouchRoutes.js"; import addBlog from "./routes/addBlogRoutes.js"; import subscribe from "./routes/subscribeRoutes.js"; +import discussion from "./routes/discussionRoutes.js"; import cors from "cors"; import path from "path"; // Import path module import { fileURLToPath } from "url"; // Import fileURLToPath @@ -37,6 +38,7 @@ app.use("/api/contact", contactRoutes); app.use("/api/getInTouch", getInTouch); app.use("/api/addBlog", addBlog); app.use("/api/newsletter", subscribe); +app.use("/api/discussion", discussion); const PORT = process.env.PORT || 5000; app.listen(PORT, () => { diff --git a/backend/controllers/discussionController.js b/backend/controllers/discussionController.js new file mode 100644 index 0000000..02ebe9d --- /dev/null +++ b/backend/controllers/discussionController.js @@ -0,0 +1,49 @@ +import Question from '../models/discussion.js'; + +export const getQuestions = async (req, res) => { + try { + const questions = await Question.find(); + res.json(questions); + } catch (error) { + res.status(500).json({ error: 'Failed to retrieve questions' }); + } +}; + +// Save a new question to MongoDB +export const saveQuestion = async (req, res) => { + try { + const { content } = req.body; + + const newQuestion = new Question({ + content, + }); + + await newQuestion.save(); + + res.status(201).json(newQuestion); + } catch (error) { + res.status(500).json({ error: 'Failed to save question' }); + } +}; + +export const addAnswerToQuestion = async (req, res) => { + try { + const questionId = req.params.id; + const { answer } = req.body; + + + const updatedQuestion = await Question.findByIdAndUpdate( + questionId, + { answered: true, answer }, + { new: true } + ); + + if (updatedQuestion) { + res.json(updatedQuestion); + } else { + res.status(404).json({ error: 'Question not found' }); + } + } catch (error) { + res.status(500).json({ error: 'Failed to update question' }); + } +}; diff --git a/backend/models/discussion.js b/backend/models/discussion.js new file mode 100644 index 0000000..2c98f16 --- /dev/null +++ b/backend/models/discussion.js @@ -0,0 +1,21 @@ +import mongoose from 'mongoose'; + +// Define the schema for a question +const discussionSchema = new mongoose.Schema({ + content: { + type: String, + required: true, + }, + answered: { + type: Boolean, + default: false, + }, + answer: { + type: String, + default: '', + }, +}, { timestamps: true }); + +const Question = mongoose.model('Question', discussionSchema); + +export default Question; diff --git a/backend/routes/discussionRoutes.js b/backend/routes/discussionRoutes.js new file mode 100644 index 0000000..99bcb24 --- /dev/null +++ b/backend/routes/discussionRoutes.js @@ -0,0 +1,12 @@ +import express from 'express'; +const router = express.Router(); + +import { getQuestions, saveQuestion, addAnswerToQuestion } from '../controllers/discussionController.js'; + +router.get('/getQuestion', getQuestions); + +router.post('/postQuestion', saveQuestion); + +router.put('/:id/answer', addAnswerToQuestion); + +export default router; diff --git a/frontend/src/pages/DiscussionForum.js b/frontend/src/pages/DiscussionForum.js index 8fa5099..5fb7c02 100644 --- a/frontend/src/pages/DiscussionForum.js +++ b/frontend/src/pages/DiscussionForum.js @@ -1,32 +1,54 @@ export async function renderDiscussionForum(container) { - // Initialize questions data - let questions = JSON.parse(localStorage.getItem('questions')) || []; - - // Helper function to save questions to localStorage - const saveQuestions = () => { - localStorage.setItem('questions', JSON.stringify(questions)); + // Fetch questions data from the backend + const fetchQuestions = async () => { + try { + const response = await fetch('http://localhost:5000/api/discussion/getQuestion'); + const data = await response.json(); + return data; + } catch (error) { + console.error('Error fetching questions:', error); + return []; + } }; - // Function to handle adding a new question - const addQuestion = (content) => { + // Helper function to save a new question to the backend + const addQuestion = async (content) => { const newQuestion = { - id: Date.now(), content: content, answered: false, answer: '', }; - questions.push(newQuestion); - saveQuestions(); - render(); + try { + const response = await fetch('http://localhost:5000/api/discussion/postQuestion', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(newQuestion), + }); + const savedQuestion = await response.json(); + window.location.reload() + } catch (error) { + console.error('Error adding question:', error); + } }; - // Function to handle adding an answer to a question - const addAnswer = (questionId, answerContent) => { - questions = questions.map(q => - q.id === questionId ? { ...q, answered: true, answer: answerContent } : q - ); - saveQuestions(); - render(); + // Helper function to add an answer to a question + const addAnswer = async (questionId, answerContent) => { + const answer = { answer: answerContent }; + try { + const response = await fetch(`http://localhost:5000/api/discussion/${questionId}/answer`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(answer), + }); + const updatedQuestion = await response.json(); + window.location.reload() + } catch (error) { + console.error('Error adding answer:', error); + } }; // Function to render the Question Card @@ -55,12 +77,13 @@ export async function renderDiscussionForum(container) { answerSection.appendChild(answerText); card.appendChild(answerSection); } else { - const answerForm = renderAnswerForm(question.id); + const answerForm = renderAnswerForm(question._id); card.appendChild(answerForm); } return card; }; + // Function to render the Question Form const renderQuestionForm = () => { const formContainer = document.createElement('div'); @@ -117,93 +140,55 @@ export async function renderDiscussionForum(container) { return formContainer; }; - - // Function to render the list of questions - const renderQuestionList = () => { - // Create container divs for unanswered and answered sections + const renderQuestionList = async () => { + const questions = await fetchQuestions(); + + // Create containers for unanswered and answered sections const unansweredContainer = document.createElement('div'); const answeredContainer = document.createElement('div'); // Create titles for unanswered and answered questions + const unansweredTitleContainer = document.createElement('div'); const unansweredTitle = document.createElement('h2'); unansweredTitle.textContent = 'Unanswered Questions'; unansweredTitle.classList.add('text-2xl', 'font-semibold', 'mb-4', 'text-gray-800', 'dark:text-white'); + const answeredTitleContainer = document.createElement('div'); const answeredTitle = document.createElement('h2'); answeredTitle.textContent = 'Answered Questions'; answeredTitle.classList.add('text-2xl', 'font-semibold', 'mb-4', 'text-gray-800', 'dark:text-white', 'mt-8'); // Append the titles to their respective containers - unansweredContainer.appendChild(unansweredTitle); - answeredContainer.appendChild(answeredTitle); + unansweredTitleContainer.appendChild(unansweredTitle); + answeredTitleContainer.appendChild(answeredTitle); - // Filter questions into answered and unanswered arrays - const unansweredQuestions = questions.filter(q => !q.answered); - const answeredQuestions = questions.filter(q => q.answered); - - // Create a wrapper div for unanswered questions + // Create containers for the question cards const unansweredCardsContainer = document.createElement('div'); - unansweredCardsContainer.classList.add('flex', 'gap-9', 'flex-wrap'); // Add space between cards - - // Handle unanswered questions - if (unansweredQuestions.length === 0) { - const noUnansweredMessage = document.createElement('p'); - noUnansweredMessage.textContent = 'No unanswered questions yet.'; - unansweredCardsContainer.appendChild(noUnansweredMessage); - } else { - unansweredQuestions.forEach(q => { - const questionCard = renderQuestionCard(q); - unansweredCardsContainer.appendChild(questionCard); - }); - } - - // Create a wrapper div for answered questions const answeredCardsContainer = document.createElement('div'); - answeredCardsContainer.classList.add('flex', 'gap-9', 'flex-wrap'); // Add space between cards + answeredCardsContainer.classList.add('flex', 'flex-wrap', 'gap-5') + unansweredCardsContainer.classList.add('flex', 'flex-wrap', 'gap-5') - // Handle answered questions - if (answeredQuestions.length === 0) { - const noAnsweredMessage = document.createElement('p'); - noAnsweredMessage.textContent = 'No answered questions yet.'; - answeredCardsContainer.appendChild(noAnsweredMessage); - } else { - answeredQuestions.forEach(q => { - const questionCard = renderQuestionCard(q); + questions.forEach((question) => { + const questionCard = renderQuestionCard(question); + if (question.answered) { answeredCardsContainer.appendChild(questionCard); - }); - } - - // Append the card containers to their respective sections - unansweredContainer.appendChild(unansweredCardsContainer); - answeredContainer.appendChild(answeredCardsContainer); - - // Append both sections to the container - container.appendChild(unansweredContainer); - container.appendChild(answeredContainer); - }; - - - - const render = () => { - container.innerHTML = ''; // Clear the container - - // Create the heading for the forum - const heading = document.createElement('h1'); - heading.textContent = 'Wordwise Discussion Forum'; - heading.classList.add('text-3xl', 'font-bold', 'mb-6', 'text-gray-800', 'dark:text-white'); - container.appendChild(heading); + } else { + unansweredCardsContainer.appendChild(questionCard); + } + }); - // First, append the question form at the top - const questionForm = renderQuestionForm(); - container.appendChild(questionForm); + // Clear previous content + container.innerHTML = ''; - // Then, append the question list (answered and unanswered) - const questionList = renderQuestionList(); - container.appendChild(questionList); + // Append new content + container.appendChild(renderQuestionForm()); + container.appendChild(unansweredTitleContainer); + container.appendChild(unansweredCardsContainer); + container.appendChild(answeredTitleContainer); + container.appendChild(answeredCardsContainer); }; - - render(); // Initial render + renderQuestionList(); } diff --git a/frontend/src/styles/output.css b/frontend/src/styles/output.css index 390a629..f971c1d 100644 --- a/frontend/src/styles/output.css +++ b/frontend/src/styles/output.css @@ -620,24 +620,20 @@ video { left: 135px; } -.top-3 { - top: 0.75rem; -} - .right-2 { right: 0.5rem; } -.right-full { - right: 100%; +.right-\[95\%\] { + right: 95%; } .top-2 { top: 0.5rem; } -.right-\[95\%\] { - right: 95%; +.top-3 { + top: 0.75rem; } .z-10 { @@ -782,10 +778,6 @@ video { height: 2rem; } -.min-h-screen { - min-height: 100vh; -} - .w-1\/3 { width: 33.333333%; } @@ -826,6 +818,10 @@ video { width: 1.5rem; } +.w-\[30\%\] { + width: 30%; +} + .w-\[70\%\] { width: 70%; } @@ -839,22 +835,6 @@ video { width: 100%; } -.w-\[27\%\] { - width: 27%; -} - -.w-\[28\%\] { - width: 28%; -} - - .w-\[29\%\] { - width: 29%; -} - -.w-\[30\%\] { - width: 30%; -} - .max-w-4xl { max-width: 56rem; } @@ -863,7 +843,6 @@ video { max-width: 1280px; } - .transform { transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } @@ -924,14 +903,18 @@ video { gap: 2rem; } -.gap-2 { - gap: 0.5rem; -} - .gap-9 { gap: 2.25rem; } +.gap-3 { + gap: 0.75rem; +} + +.gap-5 { + gap: 1.25rem; +} + .space-x-3 > :not([hidden]) ~ :not([hidden]) { --tw-space-x-reverse: 0; margin-right: calc(0.75rem * var(--tw-space-x-reverse)); @@ -1121,11 +1104,21 @@ video { background-color: rgb(220 252 231 / var(--tw-bg-opacity)); } +.bg-green-500 { + --tw-bg-opacity: 1; + background-color: rgb(34 197 94 / var(--tw-bg-opacity)); +} + .bg-indigo-100 { --tw-bg-opacity: 1; background-color: rgb(224 231 255 / var(--tw-bg-opacity)); } +.bg-indigo-500 { + --tw-bg-opacity: 1; + background-color: rgb(99 102 241 / var(--tw-bg-opacity)); +} + .bg-indigo-600 { --tw-bg-opacity: 1; background-color: rgb(79 70 229 / var(--tw-bg-opacity)); @@ -1161,35 +1154,15 @@ video { background-color: rgb(254 249 195 / var(--tw-bg-opacity)); } - .bg-green-500 { - --tw-bg-opacity: 1; - background-color: rgb(34 197 94 / var(--tw-bg-opacity)); -} - -.bg-indigo-500 { - --tw-bg-opacity: 1; - background-color: rgb(99 102 241 / var(--tw-bg-opacity)); -} - -.bg-gradient-to-r { - background-image: linear-gradient(to right, var(--tw-gradient-stops)); -} - -.from-indigo-100 { - --tw-gradient-from: #e0e7ff var(--tw-gradient-from-position); - --tw-gradient-to: rgb(224 231 255 / 0) var(--tw-gradient-to-position); - --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); -} - -.to-purple-100 { - --tw-gradient-to: #f3e8ff var(--tw-gradient-to-position); -} - .object-cover { -o-object-fit: cover; object-fit: cover; } +.p-1 { + padding: 0.25rem; +} + .p-2 { padding: 0.5rem; } @@ -1206,6 +1179,10 @@ video { padding: 1rem; } +.p-5 { + padding: 1.25rem; +} + .p-6 { padding: 1.5rem; } @@ -1214,12 +1191,6 @@ video { padding: 2rem; } - .p-5 { - padding: 1.25rem; - .p-1 { - padding: 0.25rem; - } - .px-12 { padding-left: 3rem; padding-right: 3rem; @@ -1352,11 +1323,6 @@ video { line-height: 1.75rem; } -.text-xs { - font-size: 0.75rem; - line-height: 1rem; -} - .font-bold { font-weight: 700; } @@ -1495,12 +1461,6 @@ video { color: rgb(133 77 14 / var(--tw-text-opacity)); } - .text-indigo-400 { - --tw-text-opacity: 1; - color: rgb(129 140 248 / var(--tw-text-opacity)); -} - - .shadow-lg { --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); @@ -1519,12 +1479,6 @@ video { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } -.shadow { - --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - .shadow-gray-200 { --tw-shadow-color: #e5e7eb; --tw-shadow: var(--tw-shadow-colored); @@ -1608,12 +1562,6 @@ body.dark-mode { padding: 20px; } -.hover\:scale-105:hover { - --tw-scale-x: 1.05; - --tw-scale-y: 1.05; - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} - .hover\:bg-\[\#753ff1\]:hover { --tw-bg-opacity: 1; background-color: rgb(117 63 241 / var(--tw-bg-opacity)); @@ -1639,6 +1587,16 @@ body.dark-mode { background-color: rgb(107 114 128 / var(--tw-bg-opacity)); } +.hover\:bg-green-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(22 163 74 / var(--tw-bg-opacity)); +} + +.hover\:bg-indigo-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(79 70 229 / var(--tw-bg-opacity)); +} + .hover\:bg-indigo-700:hover { --tw-bg-opacity: 1; background-color: rgb(67 56 202 / var(--tw-bg-opacity)); @@ -1669,16 +1627,6 @@ body.dark-mode { background-color: rgb(255 255 255 / var(--tw-bg-opacity)); } -.hover\:bg-green-600:hover { - --tw-bg-opacity: 1; - background-color: rgb(22 163 74 / var(--tw-bg-opacity)); -} - -.hover\:bg-indigo-600:hover { - --tw-bg-opacity: 1; - background-color: rgb(79 70 229 / var(--tw-bg-opacity)); -} - .hover\:text-blue-700:hover { --tw-text-opacity: 1; color: rgb(29 78 216 / var(--tw-text-opacity)); @@ -1694,6 +1642,11 @@ body.dark-mode { color: rgb(17 24 39 / var(--tw-text-opacity)); } +.hover\:text-indigo-500:hover { + --tw-text-opacity: 1; + color: rgb(99 102 241 / var(--tw-text-opacity)); +} + .hover\:text-indigo-800:hover { --tw-text-opacity: 1; color: rgb(55 48 163 / var(--tw-text-opacity)); @@ -1709,11 +1662,6 @@ body.dark-mode { color: rgb(255 255 255 / var(--tw-text-opacity)); } -.hover\:text-indigo-500:hover { - --tw-text-opacity: 1; - color: rgb(99 102 241 / var(--tw-text-opacity)); -} - .hover\:underline:hover { text-decoration-line: underline; } @@ -1770,6 +1718,11 @@ body.dark-mode { --tw-ring-color: rgb(107 114 128 / var(--tw-ring-opacity)); } +.focus\:ring-green-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(34 197 94 / var(--tw-ring-opacity)); +} + .focus\:ring-indigo-200:focus { --tw-ring-opacity: 1; --tw-ring-color: rgb(199 210 254 / var(--tw-ring-opacity)); @@ -1785,11 +1738,6 @@ body.dark-mode { --tw-ring-color: rgb(248 113 113 / var(--tw-ring-opacity)); } -.focus\:ring-green-500:focus { - --tw-ring-opacity: 1; - --tw-ring-color: rgb(34 197 94 / var(--tw-ring-opacity)); -} - .focus\:ring-opacity-50:focus { --tw-ring-opacity: 0.5; }