Integrate live blockchain data for proposals page

- Updated ProposalsList to use useGovernance hook
- Fetches treasury proposals and democracy referenda from blockchain
- Displays both proposal types in unified list:
  - Treasury proposals: Show requested amount, beneficiary, proposer
  - Democracy referenda: Show aye/nay votes, voting threshold, end block
- Added loading state with spinner
- Added error handling with user-friendly messages
- Added "Live Blockchain Data" badge
- Format token amounts from blockchain units (12 decimals to HEZ)
- Empty state when no proposals exist
- Token symbol consistency: All amounts show as HEZ
- Auto-refreshes every 30 seconds via useGovernance hook

All proposals and referenda now display live blockchain data.
This commit is contained in:
Claude
2025-11-14 11:03:27 +00:00
parent 88a9678f8b
commit 7789d926ec
+85 -32
View File
@@ -1,9 +1,12 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { FileText, Vote, Clock, TrendingUp, Users, AlertCircle } from 'lucide-react'; import { FileText, Vote, Clock, TrendingUp, Users, AlertCircle, Loader2, Activity } from 'lucide-react';
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
import { Badge } from '../ui/badge'; import { Badge } from '../ui/badge';
import { Button } from '../ui/button'; import { Button } from '../ui/button';
import { Progress } from '../ui/progress'; import { Progress } from '../ui/progress';
import { Alert, AlertDescription } from '../ui/alert';
import { useGovernance } from '@/hooks/useGovernance';
import { formatNumber } from '@/lib/utils';
interface Proposal { interface Proposal {
id: number; id: number;
@@ -21,35 +24,46 @@ interface Proposal {
} }
const ProposalsList: React.FC = () => { const ProposalsList: React.FC = () => {
const [proposals] = useState<Proposal[]>([ const { proposals: treasuryProposals, referenda, loading, error } = useGovernance();
{
id: 1, // Format token amounts from blockchain units (12 decimals for HEZ)
title: 'Treasury Allocation for Development Fund', const formatTokenAmount = (amount: string) => {
description: 'Allocate 500,000 PEZ for ecosystem development', const value = BigInt(amount);
proposer: '5GrwvaEF...', return formatNumber(Number(value) / 1e12, 2);
type: 'treasury', };
status: 'active',
ayeVotes: 156, // Convert blockchain data to UI format
nayVotes: 45, const proposals: Proposal[] = [
totalVotes: 201, // Treasury proposals
quorum: 60, ...treasuryProposals.map(p => ({
deadline: '2 days', id: p.proposalIndex,
requestedAmount: '500,000 PEZ' title: `Treasury Proposal #${p.proposalIndex}`,
}, description: `Requesting ${formatTokenAmount(p.value)} HEZ for ${p.beneficiary.substring(0, 10)}...`,
{ proposer: p.proposer,
id: 2, type: 'treasury' as const,
title: 'Update Staking Parameters', status: p.status as 'active' | 'passed' | 'rejected' | 'pending',
description: 'Increase minimum stake requirement to 1000 HEZ', ayeVotes: 0, // Treasury proposals don't have votes until they become referenda
proposer: '5FHneW46...', nayVotes: 0,
type: 'executive', totalVotes: 0,
status: 'active', quorum: 0,
ayeVotes: 89, deadline: 'Pending referendum',
nayVotes: 112, requestedAmount: `${formatTokenAmount(p.value)} HEZ`
totalVotes: 201, })),
quorum: 60, // Democracy referenda
deadline: '5 days' ...referenda.map(r => ({
} id: r.index,
]); title: `Referendum #${r.index}`,
description: `Voting on proposal with ${r.threshold} threshold`,
proposer: 'Democracy',
type: 'executive' as const,
status: r.status as 'active' | 'passed' | 'rejected' | 'pending',
ayeVotes: Number(BigInt(r.ayeVotes) / BigInt(1e12)),
nayVotes: Number(BigInt(r.nayVotes) / BigInt(1e12)),
totalVotes: Number((BigInt(r.ayeVotes) + BigInt(r.nayVotes)) / BigInt(1e12)),
quorum: 50,
deadline: `Block ${r.end}`,
}))
];
const getStatusBadge = (status: string) => { const getStatusBadge = (status: string) => {
switch(status) { switch(status) {
@@ -69,9 +83,47 @@ const ProposalsList: React.FC = () => {
} }
}; };
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<Loader2 className="h-8 w-8 animate-spin text-primary mr-3" />
<span className="text-muted-foreground">Loading proposals from blockchain...</span>
</div>
);
}
if (error) {
return (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>
Failed to load proposals: {error}
</AlertDescription>
</Alert>
);
}
return ( return (
<div className="space-y-4"> <div className="space-y-4">
{proposals.map((proposal) => { {/* Live Data Badge */}
<div className="flex items-center gap-2 mb-4">
<Badge variant="outline" className="bg-green-500/10 text-green-500 border-green-500/20">
<Activity className="h-3 w-3 mr-1" />
Live Blockchain Data
</Badge>
<span className="text-sm text-muted-foreground">
{proposals.length} active proposals & referenda
</span>
</div>
{proposals.length === 0 ? (
<Card className="bg-gray-900/50 border-gray-800">
<CardContent className="pt-6 text-center text-gray-500">
No active proposals or referenda found on the blockchain.
</CardContent>
</Card>
) : (
proposals.map((proposal) => {
const ayePercentage = (proposal.ayeVotes / proposal.totalVotes) * 100; const ayePercentage = (proposal.ayeVotes / proposal.totalVotes) * 100;
const nayPercentage = (proposal.nayVotes / proposal.totalVotes) * 100; const nayPercentage = (proposal.nayVotes / proposal.totalVotes) * 100;
const quorumReached = (proposal.totalVotes / 300) * 100 >= proposal.quorum; const quorumReached = (proposal.totalVotes / 300) * 100 >= proposal.quorum;
@@ -148,7 +200,8 @@ const ProposalsList: React.FC = () => {
</CardContent> </CardContent>
</Card> </Card>
); );
})} })
)}
</div> </div>
); );
}; };