Implement moderated Q&A board: questions only display after being answered

This commit is contained in:
Shanmuga Krishnan S M
2026-07-27 08:39:58 +05:30
parent e993cc4ec9
commit 42933f88cc
3 changed files with 41 additions and 29 deletions

View File

@@ -28,7 +28,7 @@ public class CommunityQuestionController {
@GetMapping @GetMapping
public List<CommunityQuestion> getAllQuestions() { public List<CommunityQuestion> getAllQuestions() {
return questionRepository.findAll(); return questionRepository.findByIsAnsweredTrue();
} }
@PostMapping @PostMapping

View File

@@ -3,7 +3,9 @@ package com.rit.portal.repository;
import com.rit.portal.entity.CommunityQuestion; import com.rit.portal.entity.CommunityQuestion;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List;
@Repository @Repository
public interface CommunityQuestionRepository extends JpaRepository<CommunityQuestion, Integer> { public interface CommunityQuestionRepository extends JpaRepository<CommunityQuestion, Integer> {
List<CommunityQuestion> findByIsAnsweredTrue();
} }

View File

@@ -76,6 +76,8 @@ export default function Community() {
const [currentPage, setCurrentPage] = useState(0); const [currentPage, setCurrentPage] = useState(0);
const [questions, setQuestions] = useState<any[]>([]); const [questions, setQuestions] = useState<any[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [showSuccessBanner, setShowSuccessBanner] = useState(false);
const [postError, setPostError] = useState<string | null>(null);
const toggleAnswers = (id: string) => { const toggleAnswers = (id: string) => {
setExpandedQuestionIds((prev) => { setExpandedQuestionIds((prev) => {
@@ -229,48 +231,33 @@ export default function Community() {
); );
const finalTags = detectedTags.length > 0 ? detectedTags : ['fresher', 'general']; const finalTags = detectedTags.length > 0 ? detectedTags : ['fresher', 'general'];
const newQuestion = { setPostError(null);
title: questionText.split('\n')[0].substring(0, 100) || "Q&A Question", setShowSuccessBanner(false);
body: questionText,
author: displayAuthor,
tags: finalTags,
upvotes: 0,
isAnswered: false,
createdAt: new Date().toISOString(),
answers: []
};
try { try {
const response = await fetch(getBackendUrl('/api/questions'), { const response = await fetch(getBackendUrl('/api/questions'), {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ body: JSON.stringify({
title: newQuestion.title, title: questionText.split('\n')[0].substring(0, 100) || "Q&A Question",
body: newQuestion.body, body: questionText,
author: newQuestion.author, author: displayAuthor,
tags: newQuestion.tags tags: finalTags
}) })
}); });
if (response.ok) { if (response.ok) {
const saved = await response.json(); setShowSuccessBanner(true);
setQuestions(prev => {
// Prepend saved question and filter out duplicate placeholders
const filtered = prev.filter(q => q.id.toString() !== saved.id.toString());
return [saved, ...filtered];
});
setQuestionText(''); setQuestionText('');
setAuthorName(''); setAuthorName('');
// Hide success banner after 6 seconds
setTimeout(() => setShowSuccessBanner(false), 6000);
} else { } else {
setQuestions(prev => [{ ...newQuestion, id: String(Date.now()) }, ...prev]); setPostError("Failed to submit your question. Please try again later.");
setQuestionText('');
setAuthorName('');
} }
} catch (error) { } catch (error) {
console.error("Error saving question:", error); console.error("Error saving question:", error);
setQuestions(prev => [{ ...newQuestion, id: String(Date.now()) }, ...prev]); setPostError("Unable to connect to the server. Please check your connection.");
setQuestionText('');
setAuthorName('');
} }
}; };
@@ -293,6 +280,29 @@ export default function Community() {
<div className="lg:col-span-2"> {/* Ask Question Box */} <div className="lg:col-span-2"> {/* Ask Question Box */}
<div className="bg-white border border-[#E5E7EB] rounded-2xl p-6 mb-8" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}> <div className="bg-white border border-[#E5E7EB] rounded-2xl p-6 mb-8" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
<h3 className="text-base font-semibold text-[#1E293B] mb-3 tracking-tight" style={{ fontFamily: 'Poppins, sans-serif' }}>Ask a Question</h3> <h3 className="text-base font-semibold text-[#1E293B] mb-3 tracking-tight" style={{ fontFamily: 'Poppins, sans-serif' }}>Ask a Question</h3>
{showSuccessBanner && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="mb-4 p-4.5 bg-emerald-50 border border-emerald-200 text-emerald-800 rounded-xl text-xs font-medium leading-relaxed"
style={{ fontFamily: 'Inter, sans-serif' }}
>
🎉 <strong>Question submitted!</strong> Your question has been forwarded to senior volunteers. Once they review and answer it, it will be published here on the community Q&A board.
</motion.div>
)}
{postError && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="mb-4 p-4 bg-rose-50 border border-rose-200 text-rose-850 rounded-xl text-xs font-semibold"
style={{ fontFamily: 'Inter, sans-serif' }}
>
{postError}
</motion.div>
)}
<textarea <textarea
value={questionText} value={questionText}
onChange={(e) => setQuestionText(e.target.value)} onChange={(e) => setQuestionText(e.target.value)}
@@ -310,8 +320,8 @@ export default function Community() {
style={{ fontFamily: 'Inter, sans-serif' }} style={{ fontFamily: 'Inter, sans-serif' }}
/> />
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-[11px] text-slate-400" style={{ fontFamily: 'Inter, sans-serif' }}> <span className="text-[11px] text-slate-400 max-w-[70%] leading-relaxed" style={{ fontFamily: 'Inter, sans-serif' }}>
Your question will be visible to all students Your question will be sent to the senior helpers and published once answered.
</span> </span>
<motion.button <motion.button
whileHover={{ scale: 1.01 }} whileHover={{ scale: 1.01 }}