VoteProgress.tsx
1 import type { PullRequest } from '../../types/vote' 2 import { calculateVoteProgress } from '../../services/chain' 3 4 interface VoteProgressProps { 5 pr: PullRequest 6 totalGovernors: number 7 } 8 9 export default function VoteProgress({ pr, totalGovernors }: VoteProgressProps) { 10 const progress = calculateVoteProgress(pr, totalGovernors) 11 12 // Format time remaining 13 const formatTimeRemaining = (seconds: number | null): string => { 14 if (seconds === null || seconds <= 0) return 'Ended' 15 16 const days = Math.floor(seconds / 86400) 17 const hours = Math.floor((seconds % 86400) / 3600) 18 const minutes = Math.floor((seconds % 3600) / 60) 19 20 if (days > 0) return `${days}d ${hours}h remaining` 21 if (hours > 0) return `${hours}h ${minutes}m remaining` 22 return `${minutes}m remaining` 23 } 24 25 const totalVotes = pr.yesVotes + pr.noVotes 26 const yesWidth = totalVotes > 0 ? (pr.yesVotes / totalVotes) * 100 : 0 27 const noWidth = totalVotes > 0 ? (pr.noVotes / totalVotes) * 100 : 0 28 29 return ( 30 <div className="space-y-3"> 31 {/* Progress bar */} 32 <div className="h-4 bg-gray-200 rounded-full overflow-hidden flex"> 33 <div 34 className="bg-green-500 transition-all duration-300" 35 style={{ width: `${yesWidth}%` }} 36 /> 37 <div 38 className="bg-red-500 transition-all duration-300" 39 style={{ width: `${noWidth}%` }} 40 /> 41 </div> 42 43 {/* Vote counts */} 44 <div className="flex justify-between text-sm"> 45 <div className="flex items-center gap-2"> 46 <span className="inline-block w-3 h-3 bg-green-500 rounded-full" /> 47 <span className="text-gray-700"> 48 Yes: <span className="font-semibold">{pr.yesVotes}</span> 49 </span> 50 <span className="text-gray-400">({progress.percentYes.toFixed(1)}%)</span> 51 </div> 52 <div className="flex items-center gap-2"> 53 <span className="text-gray-400">({progress.percentNo.toFixed(1)}%)</span> 54 <span className="text-gray-700"> 55 No: <span className="font-semibold">{pr.noVotes}</span> 56 </span> 57 <span className="inline-block w-3 h-3 bg-red-500 rounded-full" /> 58 </div> 59 </div> 60 61 {/* Threshold indicator */} 62 <div className="flex items-center justify-between text-sm"> 63 <div className="text-gray-500"> 64 Threshold: <span className="font-medium">{progress.threshold}</span> votes (67%) 65 </div> 66 <div 67 className={`font-medium ${ 68 progress.isPassing ? 'text-green-600' : 'text-gray-500' 69 }`} 70 > 71 {progress.isPassing ? 'Passing' : 'Not passing'} 72 </div> 73 </div> 74 75 {/* Time remaining */} 76 {pr.status === 'voting' && ( 77 <div className="text-sm text-gray-500 text-center"> 78 {formatTimeRemaining(progress.timeRemaining)} 79 </div> 80 )} 81 </div> 82 ) 83 }