diff --git a/client/app/community/page.tsx b/client/app/community/page.tsx new file mode 100644 index 0000000..85efd99 --- /dev/null +++ b/client/app/community/page.tsx @@ -0,0 +1,539 @@ +"use client"; +import React, { useState } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { + ChatBubbleLeftRightIcon, + FireIcon, + ClockIcon, + CheckBadgeIcon, + ArrowUpIcon, + ArrowDownIcon, + MagnifyingGlassIcon, + PlusCircleIcon, + TagIcon, + UserCircleIcon, + CalendarIcon, + ChatBubbleBottomCenterTextIcon, +} from "@heroicons/react/24/outline"; +import { HeartIcon as HeartIconSolid } from "@heroicons/react/24/solid"; + +interface Discussion { + id: number; + title: string; + content: string; + author: string; + authorBadge?: string; + votes: number; + replies: number; + views: number; + category: string; + tags: string[]; + timestamp: string; + isPinned?: boolean; +} + +const mockDiscussions: Discussion[] = [ + { + id: 1, + title: "Best voting algorithm for university elections?", + content: + "We're planning to use Agora for our university student council elections. Which voting algorithm would you recommend for 500+ voters?", + author: "Alex Chen", + authorBadge: "Verified Creator", + votes: 24, + replies: 15, + views: 342, + category: "Voting Algorithms", + tags: ["borda", "irv", "advice"], + timestamp: "2 hours ago", + isPinned: true, + }, + { + id: 2, + title: "How to ensure voter anonymity with ZK-SNARKs?", + content: + "I'm interested in implementing anonymous voting. Can someone explain how the zero-knowledge proofs work in Agora?", + author: "Sarah Johnson", + votes: 18, + replies: 8, + views: 256, + category: "Security & Privacy", + tags: ["zk-snarks", "anonymity", "privacy"], + timestamp: "5 hours ago", + }, + { + id: 3, + title: "Smart contract deployment costs on different networks", + content: + "Has anyone compared gas fees for deploying elections on Sepolia vs Polygon Amoy? Looking for real-world data.", + author: "Michael Torres", + authorBadge: "Top Contributor", + votes: 32, + replies: 22, + views: 489, + category: "Blockchain & Gas", + tags: ["gas-fees", "deployment", "networks"], + timestamp: "1 day ago", + }, + { + id: 4, + title: "Feature request: Multi-signature election creation", + content: + "Would be great to have multiple admins approve election creation. Anyone else need this feature?", + author: "Emma Davis", + votes: 15, + replies: 12, + views: 178, + category: "Feature Requests", + tags: ["multi-sig", "governance", "feature"], + timestamp: "2 days ago", + }, + { + id: 5, + title: "Integrating Agora with Discord for community voting", + content: + "Working on a Discord bot that integrates with Agora. Has anyone done this before? Looking for guidance.", + author: "David Kim", + votes: 28, + replies: 19, + views: 412, + category: "Integrations", + tags: ["discord", "bot", "integration"], + timestamp: "3 days ago", + }, + { + id: 6, + title: "Troubleshooting: Wallet won't connect on mobile", + content: + "Users reporting issues connecting MetaMask on mobile browsers. Any solutions?", + author: "Lisa Wang", + votes: 12, + replies: 7, + views: 145, + category: "Technical Support", + tags: ["mobile", "wallet", "bug"], + timestamp: "4 days ago", + }, +]; + +const categories = [ + { name: "All Topics", icon: ChatBubbleLeftRightIcon, count: 156 }, + { name: "Voting Algorithms", icon: CheckBadgeIcon, count: 42 }, + { name: "Security & Privacy", icon: FireIcon, count: 38 }, + { name: "Blockchain & Gas", icon: TagIcon, count: 29 }, + { name: "Feature Requests", icon: PlusCircleIcon, count: 24 }, + { name: "Integrations", icon: ChatBubbleBottomCenterTextIcon, count: 15 }, + { name: "Technical Support", icon: ClockIcon, count: 8 }, +]; + +export default function CommunityPage() { + const [searchQuery, setSearchQuery] = useState(""); + const [selectedCategory, setSelectedCategory] = useState("All Topics"); + const [sortBy, setSortBy] = useState<"hot" | "new" | "top">("hot"); + const [votedPosts, setVotedPosts] = useState<{ + [key: number]: "up" | "down" | null; + }>({}); + const [isNewDiscussionOpen, setIsNewDiscussionOpen] = useState(false); + const [newDiscussion, setNewDiscussion] = useState({ + title: "", + content: "", + category: "General", + tags: "", + }); + + const handleVote = (postId: number, voteType: "up" | "down") => { + setVotedPosts((prev) => ({ + ...prev, + [postId]: prev[postId] === voteType ? null : voteType, + })); + }; + + const handleCreateDiscussion = (e: React.FormEvent) => { + e.preventDefault(); + // In a real app, this would POST to an API + alert(`New discussion created:\nTitle: ${newDiscussion.title}\nCategory: ${newDiscussion.category}`); + setNewDiscussion({ title: "", content: "", category: "General", tags: "" }); + setIsNewDiscussionOpen(false); + }; + + const filteredDiscussions = mockDiscussions.filter((discussion) => { + const matchesSearch = + discussion.title.toLowerCase().includes(searchQuery.toLowerCase()) || + discussion.content.toLowerCase().includes(searchQuery.toLowerCase()) || + discussion.tags.some((tag) => + tag.toLowerCase().includes(searchQuery.toLowerCase()) + ); + const matchesCategory = + selectedCategory === "All Topics" || + discussion.category === selectedCategory; + return matchesSearch && matchesCategory; + }); + + return ( +
+
+ {/* Header */} + +
+
+

+ Community Forum +

+

+ Discuss voting algorithms, share ideas, and connect with the + community +

+
+ setIsNewDiscussionOpen(true)} + className="mt-4 md:mt-0 inline-flex items-center px-6 py-3 bg-indigo-600 hover:bg-indigo-700 text-white font-semibold rounded-lg shadow-lg transition-colors duration-200" + > + + New Discussion + +
+
+ + {/* Search and Sort */} + +
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-12 pr-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-400 transition-colors duration-200" + /> +
+
+ {(["hot", "new", "top"] as const).map((sort) => ( + + ))} +
+
+
+ +
+ {/* Categories Sidebar */} + +
+

+ Categories +

+ +
+
+ + {/* Discussions List */} +
+
+ + {filteredDiscussions.map((discussion, index) => { + const userVote = votedPosts[discussion.id]; + const displayVotes = + discussion.votes + + (userVote === "up" ? 1 : userVote === "down" ? -1 : 0); + + return ( + +
+
+ {/* Vote Section */} +
+ + + {displayVotes} + + +
+ + {/* Content Section */} +
+ {discussion.isPinned && ( + + 📌 Pinned + + )} +

+ {discussion.title} +

+

+ {discussion.content} +

+ + {/* Tags */} +
+ {discussion.tags.map((tag) => ( + + + {tag} + + ))} +
+ + {/* Meta Info */} +
+
+ + + {discussion.author} + + {discussion.authorBadge && ( + + + {discussion.authorBadge} + + )} +
+
+ + {discussion.timestamp} +
+
+ + {discussion.replies} replies +
+
+ {discussion.views} views +
+
+
+
+
+
+ ); + })} +
+ + {filteredDiscussions.length === 0 && ( +
+ +

+ No discussions found matching your search. +

+
+ )} +
+
+
+ + {/* New Discussion Modal */} + + {isNewDiscussionOpen && ( + <> + {/* Backdrop */} + setIsNewDiscussionOpen(false)} + className="fixed inset-0 bg-black bg-opacity-50 z-40" + /> + + {/* Modal */} + +
+
+

+ Start a New Discussion +

+ +
+ {/* Title */} +
+ + + setNewDiscussion({ ...newDiscussion, title: e.target.value }) + } + placeholder="What's your discussion about?" + className="w-full px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent bg-white dark:bg-gray-700 text-gray-900 dark:text-white" + /> +
+ + {/* Category */} +
+ + +
+ + {/* Content */} +
+ +