GovernorsPage.tsx
1 /** 2 * Governors Page - Governor management for Central Bank 3 */ 4 import { useEffect, useState } from 'react'; 5 import { useGovernanceStore } from '../store/governance'; 6 7 export function GovernorsPage() { 8 const { 9 governors, 10 isLoading, 11 fetchGovernors, 12 proposeAddGovernor, 13 proposeRemoveGovernor, 14 } = useGovernanceStore(); 15 16 const [showAddModal, setShowAddModal] = useState(false); 17 const [newGovernorAddress, setNewGovernorAddress] = useState(''); 18 const [newGovernorName, setNewGovernorName] = useState(''); 19 20 useEffect(() => { 21 fetchGovernors(); 22 }, [fetchGovernors]); 23 24 const handleAddGovernor = async (e: React.FormEvent) => { 25 e.preventDefault(); 26 if (!newGovernorAddress || !newGovernorName) return; 27 28 await proposeAddGovernor(newGovernorAddress, newGovernorName); 29 setNewGovernorAddress(''); 30 setNewGovernorName(''); 31 setShowAddModal(false); 32 }; 33 34 const formatVotingPower = (power: bigint): string => { 35 const num = Number(power) / 1e18; 36 return num.toLocaleString(undefined, { maximumFractionDigits: 2 }); 37 }; 38 39 const getTotalVotingPower = (): bigint => { 40 return governors.reduce((sum, g) => sum + g.votingPower, BigInt(0)); 41 }; 42 43 const getVotingPowerPercentage = (power: bigint): number => { 44 const total = getTotalVotingPower(); 45 if (total === BigInt(0)) return 0; 46 return (Number(power) / Number(total)) * 100; 47 }; 48 49 if (isLoading) { 50 return ( 51 <div className="flex items-center justify-center h-64"> 52 <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-alpha" /> 53 </div> 54 ); 55 } 56 57 return ( 58 <div className="max-w-6xl mx-auto p-6"> 59 <div className="flex justify-between items-center mb-8"> 60 <h1 className="text-2xl font-bold text-text-primary">Governor Management</h1> 61 <button 62 onClick={() => setShowAddModal(true)} 63 className="px-4 py-2 bg-alpha text-text-primary rounded-lg hover:bg-alpha-dark transition-colors" 64 > 65 Propose New Governor 66 </button> 67 </div> 68 69 {/* Current Governors List */} 70 <section className="mb-8"> 71 <h2 className="text-lg font-semibold text-text-primary mb-4"> 72 Current Governors 73 <span className="ml-2 px-2 py-0.5 text-xs bg-alpha text-text-primary rounded-full"> 74 {governors.length} 75 </span> 76 </h2> 77 <div className="bg-card rounded-xl overflow-hidden"> 78 {governors.length === 0 ? ( 79 <div className="p-6 text-center text-text-secondary"> 80 No governors configured 81 </div> 82 ) : ( 83 <table className="w-full"> 84 <thead className="bg-card-hover"> 85 <tr> 86 <th className="text-left px-6 py-3 text-text-secondary text-sm font-medium">Name</th> 87 <th className="text-left px-6 py-3 text-text-secondary text-sm font-medium">Address</th> 88 <th className="text-left px-6 py-3 text-text-secondary text-sm font-medium">Voting Power</th> 89 <th className="text-left px-6 py-3 text-text-secondary text-sm font-medium">Status</th> 90 <th className="text-left px-6 py-3 text-text-secondary text-sm font-medium">Last Active</th> 91 <th className="text-right px-6 py-3 text-text-secondary text-sm font-medium">Actions</th> 92 </tr> 93 </thead> 94 <tbody className="divide-y divide-border"> 95 {governors.map((governor) => ( 96 <tr key={governor.address} className="hover:bg-card-hover"> 97 <td className="px-6 py-4"> 98 <div className="flex items-center gap-3"> 99 <div className="w-10 h-10 rounded-full bg-gradient-to-br from-alpha to-alpha-dark flex items-center justify-center text-text-primary font-bold"> 100 {governor.name.charAt(0).toUpperCase()} 101 </div> 102 <span className="text-text-primary font-medium">{governor.name}</span> 103 </div> 104 </td> 105 <td className="px-6 py-4 text-text-secondary font-mono text-sm"> 106 {governor.address.slice(0, 10)}...{governor.address.slice(-8)} 107 </td> 108 <td className="px-6 py-4"> 109 <div className="flex items-center gap-2"> 110 <span className="text-text-primary">{formatVotingPower(governor.votingPower)}</span> 111 <span className="text-text-secondary text-sm"> 112 ({getVotingPowerPercentage(governor.votingPower).toFixed(1)}%) 113 </span> 114 </div> 115 </td> 116 <td className="px-6 py-4"> 117 <span className={`px-2 py-1 text-xs rounded-full ${ 118 governor.isActive ? 'bg-success/20 text-success' : 'bg-card-hover text-text-secondary' 119 }`}> 120 {governor.isActive ? 'Active' : 'Inactive'} 121 </span> 122 </td> 123 <td className="px-6 py-4 text-text-secondary"> 124 {new Date(governor.lastActiveAt).toLocaleDateString()} 125 </td> 126 <td className="px-6 py-4"> 127 <div className="flex justify-end gap-2"> 128 <button 129 onClick={() => proposeRemoveGovernor(governor.address)} 130 className="px-3 py-1 bg-error/20 text-error text-sm rounded hover:bg-error/30" 131 > 132 Propose Removal 133 </button> 134 </div> 135 </td> 136 </tr> 137 ))} 138 </tbody> 139 </table> 140 )} 141 </div> 142 </section> 143 144 {/* Voting Power Distribution */} 145 <section className="mb-8"> 146 <h2 className="text-lg font-semibold text-text-primary mb-4">Voting Power Distribution</h2> 147 <div className="bg-card rounded-xl p-6"> 148 <div className="space-y-4"> 149 {governors.map((governor) => ( 150 <div key={governor.address}> 151 <div className="flex justify-between text-sm mb-1"> 152 <span className="text-text-primary">{governor.name}</span> 153 <span className="text-text-secondary">{getVotingPowerPercentage(governor.votingPower).toFixed(1)}%</span> 154 </div> 155 <div className="h-3 bg-card-hover rounded-full overflow-hidden"> 156 <div 157 className="h-full bg-gradient-to-r from-alpha to-alpha-dark" 158 style={{ width: `${getVotingPowerPercentage(governor.votingPower)}%` }} 159 /> 160 </div> 161 </div> 162 ))} 163 </div> 164 <div className="mt-6 pt-4 border-t border-border"> 165 <div className="flex justify-between text-sm"> 166 <span className="text-text-secondary">Total Voting Power</span> 167 <span className="text-text-primary font-medium">{formatVotingPower(getTotalVotingPower())} ACDC</span> 168 </div> 169 </div> 170 </div> 171 </section> 172 173 {/* Governor Activity Metrics */} 174 <section> 175 <h2 className="text-lg font-semibold text-text-primary mb-4">Governor Activity Metrics</h2> 176 <div className="grid grid-cols-1 md:grid-cols-3 gap-4"> 177 {governors.map((governor) => ( 178 <div key={governor.address} className="bg-card rounded-xl p-6"> 179 <div className="flex items-center gap-3 mb-4"> 180 <div className="w-10 h-10 rounded-full bg-gradient-to-br from-alpha to-alpha-dark flex items-center justify-center text-text-primary font-bold"> 181 {governor.name.charAt(0).toUpperCase()} 182 </div> 183 <span className="text-text-primary font-medium">{governor.name}</span> 184 </div> 185 <div className="space-y-3"> 186 <div className="flex justify-between"> 187 <span className="text-text-secondary text-sm">Proposals Voted</span> 188 <span className="text-text-primary">{governor.proposalsVoted}</span> 189 </div> 190 <div className="flex justify-between"> 191 <span className="text-text-secondary text-sm">Proposals Created</span> 192 <span className="text-text-primary">{governor.proposalsCreated}</span> 193 </div> 194 <div className="flex justify-between"> 195 <span className="text-text-secondary text-sm">Participation Rate</span> 196 <span className="text-text-primary">{governor.participationRate.toFixed(1)}%</span> 197 </div> 198 </div> 199 </div> 200 ))} 201 </div> 202 </section> 203 204 {/* Add Governor Modal */} 205 {showAddModal && ( 206 <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"> 207 <div className="bg-card rounded-xl p-6 w-full max-w-md"> 208 <h3 className="text-xl font-bold text-text-primary mb-4">Propose New Governor</h3> 209 <form onSubmit={handleAddGovernor}> 210 <div className="mb-4"> 211 <label className="block text-text-secondary text-sm mb-2">Governor Name</label> 212 <input 213 type="text" 214 value={newGovernorName} 215 onChange={(e) => setNewGovernorName(e.target.value)} 216 className="w-full bg-card-hover text-text-primary px-4 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-alpha" 217 placeholder="Central Bank Representative" 218 required 219 /> 220 </div> 221 <div className="mb-6"> 222 <label className="block text-text-secondary text-sm mb-2">Wallet Address</label> 223 <input 224 type="text" 225 value={newGovernorAddress} 226 onChange={(e) => setNewGovernorAddress(e.target.value)} 227 className="w-full bg-card-hover text-text-primary px-4 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-alpha" 228 placeholder="dx1..." 229 required 230 /> 231 </div> 232 <p className="text-text-secondary text-sm mb-6"> 233 This will create a governance proposal to add a new governor. Other governors must approve this proposal for it to take effect. 234 </p> 235 <div className="flex gap-3"> 236 <button 237 type="button" 238 onClick={() => setShowAddModal(false)} 239 className="flex-1 py-2 bg-card-hover text-text-primary rounded-lg hover:bg-border-strong" 240 > 241 Cancel 242 </button> 243 <button 244 type="submit" 245 className="flex-1 py-2 bg-alpha text-text-primary rounded-lg hover:bg-alpha-dark" 246 > 247 Create Proposal 248 </button> 249 </div> 250 </form> 251 </div> 252 </div> 253 )} 254 </div> 255 ); 256 }