"use client"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; import Link from "next/link"; import { ChevronUp, ChevronDown, Check, X, Minus, ArrowRight, RotateCcw, Coins, TrendingUp, Clock, } from "lucide-react"; interface DemoProposal { id: number; title: string; description: string; score: number; userVote: number; pendingVote: number; stage: "ranking" | "voting"; yesVotes: number; noVotes: number; } const initialProposals: DemoProposal[] = [ { id: 1, title: "Allocate 15% of treasury to ecosystem grants program", description: "Fund community developers building tools and integrations for the ecosystem over the next 6 months", score: 72, userVote: 0, pendingVote: 0, stage: "ranking", yesVotes: 0, noVotes: 0, }, { id: 2, title: "Establish a community moderation council", description: "Elect 7 members to handle disputes, enforce guidelines, and maintain community standards", score: 58, userVote: 0, pendingVote: 0, stage: "ranking", yesVotes: 0, noVotes: 0, }, { id: 3, title: "Partner with University research lab for governance study", description: "Collaborate with academic researchers to analyze and improve our decision-making processes", score: 41, userVote: 0, pendingVote: 0, stage: "ranking", yesVotes: 0, noVotes: 0, }, { id: 4, title: "Create bounty program for security audits", description: "Reward external security researchers who identify vulnerabilities in our smart contracts", score: 35, userVote: 0, pendingVote: 0, stage: "ranking", yesVotes: 0, noVotes: 0, }, { id: 5, title: "Host quarterly virtual town halls", description: "Regular video conferences for community updates, Q&A sessions, and open discussion", score: 23, userVote: 0, pendingVote: 0, stage: "ranking", yesVotes: 0, noVotes: 0, }, ]; export default function DemoPage() { const [credits, setCredits] = useState(100); const [proposals, setProposals] = useState(initialProposals); const maxWeight = Math.floor(Math.sqrt(credits)); function incrementVote(proposalId: number) { setProposals((prev) => prev.map((p) => { if (p.id === proposalId) { const newPending = p.pendingVote + 1; const newCost = newPending * newPending; if (newCost <= credits) { return { ...p, pendingVote: newPending }; } } return p; }) ); } function decrementVote(proposalId: number) { setProposals((prev) => prev.map((p) => { if (p.id === proposalId) { const newPending = p.pendingVote - 1; const newCost = newPending * newPending; if (newCost <= credits) { return { ...p, pendingVote: newPending }; } } return p; }) ); } function cancelPending(proposalId: number) { setProposals((prev) => prev.map((p) => (p.id === proposalId ? { ...p, pendingVote: 0 } : p)) ); } function castVote(proposalId: number) { setProposals((prev) => prev.map((p) => { if (p.id === proposalId && p.pendingVote !== 0) { const cost = p.pendingVote * p.pendingVote; const newScore = p.score + p.pendingVote; const promoted = newScore >= 100 && p.stage === "ranking"; setCredits((c) => c - cost); return { ...p, score: newScore, userVote: p.pendingVote, pendingVote: 0, stage: promoted ? "voting" : p.stage, yesVotes: promoted ? 8 : p.yesVotes, noVotes: promoted ? 3 : p.noVotes, }; } return p; }) ); } function removeVote(proposalId: number) { setProposals((prev) => prev.map((p) => { if (p.id === proposalId && p.userVote !== 0) { const refund = p.userVote * p.userVote; setCredits((c) => c + refund); return { ...p, score: p.score - p.userVote, userVote: 0, }; } return p; }) ); } function castFinalVote(proposalId: number, vote: "yes" | "no" | "abstain") { setProposals((prev) => prev.map((p) => { if (p.id === proposalId) { return { ...p, yesVotes: vote === "yes" ? p.yesVotes + 1 : p.yesVotes, noVotes: vote === "no" ? p.noVotes + 1 : p.noVotes, }; } return p; }) ); } function resetDemo() { setCredits(100); setProposals(initialProposals); } const rankingProposals = proposals .filter((p) => p.stage === "ranking") .sort((a, b) => b.score - a.score); const votingProposals = proposals.filter((p) => p.stage === "voting"); return (
Interactive Demo

Try Quadratic Proposal Ranking

Experience how rVote works without creating an account. Click the vote arrows to rank proposals—watch how quadratic costs scale in real-time.

{/* Credits display */}
{credits} credits
Max vote: ±{maxWeight}
{/* Quadratic cost explainer */} Quadratic Voting Cost Each additional vote costs exponentially more credits
{[1, 2, 3, 4, 5].map((w) => (
{w > 0 ? "+" : ""}{w}
vote{w > 1 ? "s" : ""}
{w * w}¢
))}
{/* Ranking stage */}
Stage 1

Quadratic Ranking

Score +100 to advance →
{rankingProposals.map((proposal) => { const hasPending = proposal.pendingVote !== 0; const hasVoted = proposal.userVote !== 0; const pendingCost = proposal.pendingVote * proposal.pendingVote; const previewScore = proposal.score + proposal.pendingVote; const progressPercent = Math.min((proposal.score / 100) * 100, 100); const previewPercent = Math.min((previewScore / 100) * 100, 100); return ( 0 ? "ring-2 ring-orange-500/50 bg-orange-500/5" : "ring-2 ring-blue-500/50 bg-blue-500/5" : hasVoted ? proposal.userVote > 0 ? "bg-orange-500/5" : "bg-blue-500/5" : "" }`} >
{/* Reddit-style vote column */}
{/* Upvote button */} {/* Score display */}
0 ? "text-orange-500" : "text-blue-500" : hasVoted ? proposal.userVote > 0 ? "text-orange-500" : "text-blue-500" : "text-foreground" }`}> {hasPending ? previewScore : proposal.score}
{/* Downvote button */}
{/* Proposal content */}

{proposal.title}

{proposal.description}

{/* Progress bar */}
Progress to voting stage 0 ? "text-orange-500" : "text-blue-500") : ""}> {hasPending ? previewScore : proposal.score}/100
0 ? "bg-orange-500" : "bg-blue-500" : "bg-primary" }`} style={{ width: `${hasPending ? previewPercent : progressPercent}%` }} />
{/* Vote status / pending confirmation */} {(hasPending || hasVoted) && (
{hasPending ? ( <> 0 ? "border-orange-500/50 text-orange-600 bg-orange-500/10" : "border-blue-500/50 text-blue-600 bg-blue-500/10" } > {proposal.pendingVote > 0 ? "+" : ""}{proposal.pendingVote} vote = {pendingCost} credits ) : hasVoted && ( 0 ? "bg-orange-500/20 text-orange-600 border-orange-500/30" : "bg-blue-500/20 text-blue-600 border-blue-500/30" } > You voted: {proposal.userVote > 0 ? "+" : ""}{proposal.userVote} )}
)}
); })}
{/* Voting stage */} {votingProposals.length > 0 && (
Stage 2

Pass/Fail Voting

One member = one vote
{votingProposals.map((proposal) => { const total = proposal.yesVotes + proposal.noVotes; const yesPercent = total > 0 ? (proposal.yesVotes / total) * 100 : 50; return (
{proposal.title}
6 days left
{proposal.description}
{/* Vote bar */}
{proposal.yesVotes} Yes ({Math.round(yesPercent)}%) {proposal.noVotes} No ({Math.round(100 - yesPercent)}%)
{/* Vote buttons */}
); })}
)} {/* CTA */}

Ready to try it for real?

Create an account to start ranking and voting on community proposals. You'll get 50 credits to start and earn 10 more each day.

); }