import { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { MessageCircle, Heart, TrendingUp, ThumbsUp, Send, Shield, Lock, Smile, ChevronRight, Search, Plus, User } from 'lucide-react'; import SectionTitle from '@/components/SectionTitle/SectionTitle'; import { getBackendUrl } from '@/lib/utils'; import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer'; import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer'; import { useAuth } from '@/context/AuthContext'; const TRENDING_TAGS = ['hostel', 'academics', 'clubs', 'campus', 'canteen', 'sports', 'placement', 'library', 'cat exam', 'semester', 'labs', 'exams']; const AVATARS: Record = { 'Priya S.': 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=120&h=120&q=80', 'Ravi K.': 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=120&h=120&q=80', 'Arun M.': 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=120&h=120&q=80', }; const getAvatar = (author: string) => { return AVATARS[author] || `https://api.dicebear.com/7.x/initials/svg?seed=${encodeURIComponent(author)}`; }; const getRelativeTime = (dateString: string) => { if (!dateString) return 'just now'; // Normalize microsecond timestamps (e.g. 2026-07-22T13:28:18.174367) to standard millisecond precision let normalized = dateString; const dotIndex = dateString.indexOf('.'); if (dotIndex !== -1) { const mainPart = dateString.substring(0, dotIndex); let msPart = dateString.substring(dotIndex + 1); // Strip any trailing non-digits (like timezone offsets Z or +05:30) for truncation, then keep first 3 digits const nonDigitMatch = msPart.match(/\D/); let suffix = ''; if (nonDigitMatch && nonDigitMatch.index !== undefined) { suffix = msPart.substring(nonDigitMatch.index); msPart = msPart.substring(0, nonDigitMatch.index); } normalized = `${mainPart}.${msPart.substring(0, 3)}${suffix}`; } const now = new Date().getTime(); // Dynamic local time const past = new Date(normalized).getTime(); if (isNaN(past)) return 'just now'; const msPerMinute = 60 * 1000; const msPerHour = msPerMinute * 60; const msPerDay = msPerHour * 24; const elapsed = now - past; if (elapsed < msPerMinute) { return 'just now'; } else if (elapsed < msPerHour) { return Math.round(elapsed / msPerMinute) + 'm ago'; } else if (elapsed < msPerDay) { return Math.round(elapsed / msPerHour) + 'h ago'; } else { const days = Math.round(elapsed / msPerDay); return days === 1 ? 'yesterday' : `${days}d ago`; } }; export default function Community() { const PAGE_SIZE = 5; const [questionText, setQuestionText] = useState(''); const [authorName, setAuthorName] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [likedIds, setLikedIds] = useState>(new Set()); const [expandedQuestionIds, setExpandedQuestionIds] = useState>(new Set()); const [expandedBodyIds, setExpandedBodyIds] = useState>(new Set()); const [sortFilter, setSortFilter] = useState<'recent' | 'liked' | 'answered' | 'unanswered'>('recent'); // Client-side pagination state const [currentPage, setCurrentPage] = useState(0); const [questions, setQuestions] = useState([]); const [loading, setLoading] = useState(false); const [showSuccessBanner, setShowSuccessBanner] = useState(false); const [postError, setPostError] = useState(null); const toggleAnswers = (id: string) => { setExpandedQuestionIds((prev) => { const next = new Set(prev); if (next.has(id.toString())) { next.delete(id.toString()); } else { next.add(id.toString()); } return next; }); }; const fetchQuestions = async () => { setLoading(true); try { const response = await fetch(getBackendUrl('/api/questions')); if (response.ok) { const data = await response.json(); // Backend returns oldest-first, reverse to show newest first setQuestions(Array.isArray(data) ? [...data].reverse() : []); } else { setQuestions([]); } } catch { // Backend unavailable — show empty state setQuestions([]); } finally { setLoading(false); } }; useEffect(() => { const savedLikes = localStorage.getItem('qa-liked-ids'); if (savedLikes) { try { setLikedIds(new Set(JSON.parse(savedLikes))); } catch (e) { console.error('Failed to parse liked IDs from localStorage', e); } } fetchQuestions(); }, []); const getAnswersCount = (q: any) => { if (Array.isArray(q.answers)) return q.answers.length; if (typeof q.answers === 'number') return q.answers; return 0; }; const getVotesCount = (q: any) => { const votesVal = typeof q.upvotes === 'number' ? q.upvotes : (typeof q.votes === 'number' ? q.votes : 0); return votesVal; }; // Client-side filter and sorting derived state let processedQuestions = questions.filter((q) => { const titleVal = q.title || ""; const tagsVal = q.tags || []; return titleVal.toLowerCase().includes(searchQuery.toLowerCase()) || tagsVal.some((t: string) => t.toLowerCase().includes(searchQuery.toLowerCase())); }); if (sortFilter === 'recent') { processedQuestions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); } else if (sortFilter === 'liked') { processedQuestions.sort((a, b) => getVotesCount(b) - getVotesCount(a)); } else if (sortFilter === 'answered') { processedQuestions.sort((a, b) => getAnswersCount(b) - getAnswersCount(a)); } else if (sortFilter === 'unanswered') { processedQuestions = processedQuestions.filter(q => !q.isAnswered && getAnswersCount(q) === 0); } const filteredQuestions = processedQuestions; const totalPages = Math.max(1, Math.ceil(filteredQuestions.length / PAGE_SIZE)); const paginatedQuestions = filteredQuestions.slice(currentPage * PAGE_SIZE, (currentPage + 1) * PAGE_SIZE); // Reset page when search or filter changes useEffect(() => { setCurrentPage(0); }, [searchQuery, sortFilter]); const handlePageChange = (newPage: number) => { setExpandedQuestionIds(new Set()); // collapse any open answers setExpandedBodyIds(new Set()); // collapse bodies too setCurrentPage(newPage); // Scroll back to the top of the questions list smoothly document.getElementById('qa-questions-list')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); }; const handleLike = async (id: string) => { let isLikedNow = false; setLikedIds((prev) => { const next = new Set(prev); if (next.has(id)) { next.delete(id); isLikedNow = false; } else { next.add(id); isLikedNow = true; } localStorage.setItem('qa-liked-ids', JSON.stringify(Array.from(next))); return next; }); // Optimistically update count (+1 if liked, -1 if unliked) setQuestions((prev) => prev.map((q) => { if (q.id.toString() === id) { const votesVal = typeof q.upvotes === 'number' ? q.upvotes : (typeof q.votes === 'number' ? q.votes : 0); return { ...q, upvotes: isLikedNow ? votesVal + 1 : Math.max(0, votesVal - 1) }; } return q; }) ); try { if (isLikedNow) { await fetch(getBackendUrl(`/api/questions/${id}/upvote`), { method: 'POST' }); fetchQuestions(); } } catch (error) { console.log('Backend not available for upvote sync.', error); } }; const handleCardClick = (id: string) => { const idStr = id.toString(); setExpandedQuestionIds((prev) => { const next = new Set(prev); if (next.has(idStr)) next.delete(idStr); else next.add(idStr); return next; }); setExpandedBodyIds((prev) => { const next = new Set(prev); if (next.has(idStr)) next.delete(idStr); else next.add(idStr); return next; }); }; const handlePostQuestion = async () => { if (!questionText.trim()) return; const displayAuthor = authorName.trim() || 'Anonymous'; // Auto-detect hashtags based on question text content const detectedTags = TRENDING_TAGS.filter(tag => questionText.toLowerCase().includes(tag.toLowerCase()) ); const finalTags = detectedTags.length > 0 ? detectedTags : ['fresher', 'general']; setPostError(null); setShowSuccessBanner(false); try { const response = await fetch(getBackendUrl('/api/questions'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: questionText.split('\n')[0].substring(0, 100) || "Q&A Question", body: questionText, author: displayAuthor, tags: finalTags }) }); if (response.ok) { setShowSuccessBanner(true); setQuestionText(''); setAuthorName(''); // Hide success banner after 6 seconds setTimeout(() => setShowSuccessBanner(false), 6000); } else { setPostError("Failed to submit your question. Please try again later."); } } catch (error) { console.error("Error saving question:", error); setPostError("Unable to connect to the server. Please check your connection."); } }; return (
{/* Header */}

RIT Community

Ask questions and connect with fellow RIT students.

{/* Ask Question Box */}

Ask a Question

{showSuccessBanner && ( 🎉 Question submitted! 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. )} {postError && ( ⚠️ {postError} )}