VotesPage.tsx
1 import { useEffect, useState } from 'react' 2 import { Link } from 'react-router-dom' 3 import { useVotesStore } from '../store/votes' 4 import VoteProgress from '../components/voting/VoteProgress' 5 import type { Chain, PRStatus } from '../types/vote' 6 7 export default function VotesPage() { 8 const { activeVotes, isLoadingVotes, loadActiveVotes } = useVotesStore() 9 const [chainFilter, setChainFilter] = useState<Chain | 'all'>('all') 10 const [statusFilter, setStatusFilter] = useState<PRStatus | 'all'>('voting') 11 12 // Mock total governors for now - would come from chain 13 const totalGovernors = 10 14 15 useEffect(() => { 16 loadActiveVotes(chainFilter === 'all' ? undefined : chainFilter) 17 }, [chainFilter, loadActiveVotes]) 18 19 const filteredVotes = activeVotes.filter((pr) => { 20 if (statusFilter !== 'all' && pr.status !== statusFilter) return false 21 if (chainFilter !== 'all' && pr.chain !== chainFilter) return false 22 return true 23 }) 24 25 // Format deadline 26 const formatDeadline = (deadline: number | null): string => { 27 if (!deadline) return 'N/A' 28 const date = new Date(deadline * 1000) 29 return date.toLocaleDateString('en-US', { 30 month: 'short', 31 day: 'numeric', 32 hour: '2-digit', 33 minute: '2-digit', 34 }) 35 } 36 37 return ( 38 <div> 39 <div className="flex items-center justify-between mb-6"> 40 <h1 className="text-2xl font-bold">Active Votes</h1> 41 42 {/* Filters */} 43 <div className="flex gap-4"> 44 <select 45 value={chainFilter} 46 onChange={(e) => setChainFilter(e.target.value as Chain | 'all')} 47 className="px-3 py-2 border border-border-primary bg-bg-secondary text-text-primary rounded-lg text-sm" 48 > 49 <option value="all">All Chains</option> 50 <option value="alpha">Alpha (Tech)</option> 51 <option value="delta">Delta (Code)</option> 52 </select> 53 54 <select 55 value={statusFilter} 56 onChange={(e) => setStatusFilter(e.target.value as PRStatus | 'all')} 57 className="px-3 py-2 border border-border-primary bg-bg-secondary text-text-primary rounded-lg text-sm" 58 > 59 <option value="voting">Active</option> 60 <option value="passed">Passed</option> 61 <option value="failed">Failed</option> 62 <option value="draft">Draft</option> 63 <option value="all">All</option> 64 </select> 65 </div> 66 </div> 67 68 {isLoadingVotes ? ( 69 <div className="flex justify-center py-12"> 70 <div className="animate-spin h-8 w-8 border-4 border-alpha-500 border-t-transparent rounded-full" /> 71 </div> 72 ) : filteredVotes.length > 0 ? ( 73 <div className="space-y-4"> 74 {filteredVotes.map((pr) => ( 75 <div 76 key={pr.prHash} 77 className="bg-bg-secondary border border-border-primary rounded-lg p-6 hover:shadow-md transition-all" 78 > 79 <div className="flex items-start justify-between mb-4"> 80 <div> 81 <div className="flex items-center gap-2 mb-1"> 82 <span 83 className={`px-2 py-0.5 rounded text-xs font-medium ${ 84 pr.chain === 'alpha' 85 ? 'bg-alpha-100 text-alpha-700' 86 : 'bg-delta-100 text-delta-700' 87 }`} 88 > 89 {pr.chain === 'alpha' ? 'Alpha' : 'Delta'} 90 </span> 91 <span 92 className={`px-2 py-0.5 rounded text-xs font-medium ${ 93 pr.status === 'voting' 94 ? 'bg-blue-100 text-blue-700' 95 : pr.status === 'passed' 96 ? 'bg-green-100 text-green-700' 97 : pr.status === 'failed' 98 ? 'bg-red-100 text-red-700' 99 : 'bg-bg-tertiary text-text-secondary' 100 }`} 101 > 102 {pr.status} 103 </span> 104 {pr.behaviorFlags > 0 && ( 105 <span className="px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-700"> 106 Flagged 107 </span> 108 )} 109 </div> 110 <h2 className="text-lg font-semibold"> 111 PR: {pr.prHash.slice(0, 16)}... 112 </h2> 113 <p className="text-sm text-text-tertiary font-mono"> 114 Repo: {pr.repoId.slice(0, 12)}... 115 </p> 116 </div> 117 118 <div className="text-right text-sm text-text-tertiary"> 119 <div>Submitter: {pr.submitter.slice(0, 10)}...</div> 120 {pr.sponsor && <div>Sponsor: {pr.sponsor.slice(0, 10)}...</div>} 121 {pr.voteDeadline && ( 122 <div>Deadline: {formatDeadline(pr.voteDeadline)}</div> 123 )} 124 </div> 125 </div> 126 127 {/* Vote progress */} 128 <VoteProgress pr={pr} totalGovernors={totalGovernors} /> 129 130 {/* Action link */} 131 <div className="mt-4 flex justify-end"> 132 <Link 133 to={`/votes/${pr.prHash}`} 134 className="text-alpha-600 hover:text-alpha-700 text-sm font-medium" 135 > 136 View Details → 137 </Link> 138 </div> 139 </div> 140 ))} 141 </div> 142 ) : ( 143 <div className="text-center py-12 text-text-tertiary"> 144 <p>No votes found matching your filters.</p> 145 <p className="text-sm mt-2"> 146 {statusFilter === 'voting' 147 ? 'No active votes at the moment.' 148 : 'Try changing the filters.'} 149 </p> 150 </div> 151 )} 152 </div> 153 ) 154 }