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 { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer'; import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer'; import { QUESTIONS_DATA, CONFESSIONS_DATA } from '@/constants'; type Tab = 'qa' | 'confession'; const TRENDING_TAGS = ['hostel', 'academics', 'clubs', 'campus', 'canteen', 'sports', 'placement', 'library']; 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 [activeTab, setActiveTab] = useState('qa'); const [confessionText, setConfessionText] = useState(''); const [questionText, setQuestionText] = useState(''); const [authorName, setAuthorName] = useState(''); const [confessionPosted, setConfessionPosted] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [likedIds, setLikedIds] = useState>(new Set()); const [expandedQuestionIds, setExpandedQuestionIds] = useState>(new Set()); // Server-side pagination const [currentPage, setCurrentPage] = useState(0); const [totalPages, setTotalPages] = useState(1); const [questions, setQuestions] = useState([]); const [loading, setLoading] = useState(false); // Fallback static questions for when backend is unavailable const [staticQuestions] = useState(QUESTIONS_DATA); 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 (page = 0) => { setLoading(true); try { const response = await fetch(`http://localhost:8080/api/questions/paged?page=${page}&size=${PAGE_SIZE}`); if (response.ok) { const data = await response.json(); // Spring Page response: { content: [], totalPages, totalElements, number } setQuestions(data.content || []); setTotalPages(data.totalPages || 1); setCurrentPage(data.number ?? page); } else { console.error("Failed to fetch questions from backend: HTTP status", response.status); // Fallback: slice static questions const start = page * PAGE_SIZE; setQuestions(staticQuestions.slice(start, start + PAGE_SIZE)); setTotalPages(Math.ceil(staticQuestions.length / PAGE_SIZE)); setCurrentPage(page); } } catch (error) { console.error('Backend not available. Falling back to local static questions data.', error); const start = page * PAGE_SIZE; setQuestions(staticQuestions.slice(start, start + PAGE_SIZE)); setTotalPages(Math.ceil(staticQuestions.length / PAGE_SIZE)); setCurrentPage(page); } finally { setLoading(false); } }; useEffect(() => { fetchQuestions(0); }, []); 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 applied on top of current page (for search within page) const filteredQuestions = 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())); }); const handlePageChange = (newPage: number) => { setExpandedQuestionIds(new Set()); // collapse any open answers fetchQuestions(newPage); // Scroll back to the top of the questions list smoothly document.getElementById('qa-questions-list')?.scrollIntoView({ behavior: 'smooth', block: 'start' }); }; const handleConfess = () => { if (!confessionText.trim()) return; setConfessionPosted(true); setConfessionText(''); setTimeout(() => setConfessionPosted(false), 3000); }; const toggleLike = async (id: string) => { setLikedIds((prev) => { const next = new Set(prev); if (next.has(id)) next.delete(id); else next.add(id); return next; }); try { await fetch(`http://localhost:8080/api/questions/${id}/upvote`, { method: 'POST' }); fetchQuestions(); } catch (error) { console.log('Backend not available for upvote sync.', error); } }; const handlePostQuestion = async () => { if (!questionText.trim()) return; const displayAuthor = authorName.trim() || 'Anonymous'; const newQuestion = { title: questionText.split('\n')[0].substring(0, 100) || "Q&A Question", body: questionText, author: displayAuthor, tags: ['fresher', 'general'], upvotes: 0, isAnswered: false, createdAt: new Date().toISOString(), answers: [] }; try { const response = await fetch('http://localhost:8080/api/questions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: newQuestion.title, body: newQuestion.body, author: newQuestion.author, tags: newQuestion.tags }) }); if (response.ok) { const saved = await response.json(); 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(''); setAuthorName(''); } else { setQuestions(prev => [{ ...newQuestion, id: String(Date.now()) }, ...prev]); setQuestionText(''); setAuthorName(''); } } catch (error) { console.error("Error saving question:", error); setQuestions(prev => [{ ...newQuestion, id: String(Date.now()) }, ...prev]); setQuestionText(''); setAuthorName(''); } }; return (
{/* Header */}

RIT Community

Ask questions, share confessions, and connect with fellow RIT students.

{/* Tabs */}
{[ { id: 'qa' as Tab, label: 'Freshers Q&A', icon: MessageCircle }, { id: 'confession' as Tab, label: 'Confessions', icon: Heart }, ].map((tab) => ( ))}
{/* Q&A Tab */} {activeTab === 'qa' && (
{/* Ask Question Box */}

Ask a Question