mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-25 21:08:00 +00:00
Reorganize repository into monorepo structure
Restructured the project to support multiple frontend applications: - Move web app to web/ directory - Create pezkuwi-sdk-ui/ for Polkadot SDK clone (planned) - Create mobile/ directory for mobile app development - Add shared/ directory with common utilities, types, and blockchain code - Update README.md with comprehensive documentation - Remove obsolete DKSweb/ directory This monorepo structure enables better code sharing and organized development across web, mobile, and SDK UI projects.
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Users, Vote, Trophy, Clock, AlertCircle, CheckCircle } from 'lucide-react';
|
||||
|
||||
interface Election {
|
||||
id: number;
|
||||
type: 'Presidential' | 'Parliamentary' | 'Constitutional Court';
|
||||
status: 'Registration' | 'Campaign' | 'Voting' | 'Completed';
|
||||
candidates: Candidate[];
|
||||
totalVotes: number;
|
||||
endBlock: number;
|
||||
currentBlock: number;
|
||||
}
|
||||
|
||||
interface Candidate {
|
||||
id: string;
|
||||
name: string;
|
||||
votes: number;
|
||||
percentage: number;
|
||||
party?: string;
|
||||
trustScore: number;
|
||||
}
|
||||
|
||||
const ElectionsInterface: React.FC = () => {
|
||||
const [selectedElection, setSelectedElection] = useState<Election | null>(null);
|
||||
const [votedCandidates, setVotedCandidates] = useState<string[]>([]);
|
||||
|
||||
const activeElections: Election[] = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'Presidential',
|
||||
status: 'Voting',
|
||||
totalVotes: 45678,
|
||||
endBlock: 1000000,
|
||||
currentBlock: 995000,
|
||||
candidates: [
|
||||
{ id: '1', name: 'Candidate A', votes: 23456, percentage: 51.3, trustScore: 850 },
|
||||
{ id: '2', name: 'Candidate B', votes: 22222, percentage: 48.7, trustScore: 780 }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'Parliamentary',
|
||||
status: 'Registration',
|
||||
totalVotes: 0,
|
||||
endBlock: 1200000,
|
||||
currentBlock: 995000,
|
||||
candidates: []
|
||||
}
|
||||
];
|
||||
|
||||
const handleVote = (candidateId: string, electionType: string) => {
|
||||
if (electionType === 'Parliamentary') {
|
||||
setVotedCandidates(prev =>
|
||||
prev.includes(candidateId)
|
||||
? prev.filter(id => id !== candidateId)
|
||||
: [...prev, candidateId]
|
||||
);
|
||||
} else {
|
||||
setVotedCandidates([candidateId]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Tabs defaultValue="active" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="active">Active Elections</TabsTrigger>
|
||||
<TabsTrigger value="register">Register</TabsTrigger>
|
||||
<TabsTrigger value="results">Results</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="active" className="space-y-4">
|
||||
{activeElections.map(election => (
|
||||
<Card key={election.id}>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<CardTitle>{election.type} Election</CardTitle>
|
||||
<CardDescription>
|
||||
{election.status === 'Voting'
|
||||
? `${election.totalVotes.toLocaleString()} votes cast`
|
||||
: `Registration ends in ${(election.endBlock - election.currentBlock).toLocaleString()} blocks`}
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Badge variant={election.status === 'Voting' ? 'default' : 'secondary'}>
|
||||
{election.status}
|
||||
</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{election.status === 'Voting' && (
|
||||
<div className="space-y-4">
|
||||
{election.candidates.map(candidate => (
|
||||
<div key={candidate.id} className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<p className="font-medium">{candidate.name}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Trust Score: {candidate.trustScore}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="font-bold">{candidate.percentage}%</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{candidate.votes.toLocaleString()} votes
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Progress value={candidate.percentage} className="h-2" />
|
||||
<Button
|
||||
size="sm"
|
||||
variant={votedCandidates.includes(candidate.id) ? "default" : "outline"}
|
||||
onClick={() => handleVote(candidate.id, election.type)}
|
||||
className="w-full"
|
||||
>
|
||||
{votedCandidates.includes(candidate.id) ? (
|
||||
<>
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
Voted
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Vote className="w-4 h-4 mr-2" />
|
||||
Vote
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
{election.type === 'Parliamentary' && (
|
||||
<p className="text-sm text-muted-foreground text-center">
|
||||
You can select multiple candidates
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="register">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Candidate Registration</CardTitle>
|
||||
<CardDescription>
|
||||
Register as a candidate for upcoming elections
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="p-4 bg-amber-50 dark:bg-amber-900/20 rounded-lg">
|
||||
<div className="flex items-start space-x-3">
|
||||
<AlertCircle className="w-5 h-5 text-amber-600 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-medium text-amber-900 dark:text-amber-100">
|
||||
Requirements
|
||||
</p>
|
||||
<ul className="text-sm text-amber-800 dark:text-amber-200 mt-2 space-y-1">
|
||||
<li>• Minimum Trust Score: 300 (Parliamentary) / 600 (Presidential)</li>
|
||||
<li>• KYC Approved Status</li>
|
||||
<li>• Endorsements: 10 (Parliamentary) / 50 (Presidential)</li>
|
||||
<li>• Deposit: 1000 PEZ</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button className="w-full" size="lg">
|
||||
Register as Candidate
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="results">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Election Results</CardTitle>
|
||||
<CardDescription>Historical election outcomes</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div className="p-4 border rounded-lg">
|
||||
<div className="flex justify-between items-start mb-3">
|
||||
<div>
|
||||
<p className="font-medium">Presidential Election 2024</p>
|
||||
<p className="text-sm text-muted-foreground">Completed 30 days ago</p>
|
||||
</div>
|
||||
<Badge variant="outline">
|
||||
<Trophy className="w-3 h-3 mr-1" />
|
||||
Completed
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>Winner: Candidate A</span>
|
||||
<span className="font-bold">52.8%</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm text-muted-foreground">
|
||||
<span>Total Votes</span>
|
||||
<span>89,234</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm text-muted-foreground">
|
||||
<span>Turnout</span>
|
||||
<span>67.5%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ElectionsInterface;
|
||||
@@ -0,0 +1,318 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Vote, Users, Gavel, FileText, TrendingUpIcon,
|
||||
Clock, CheckCircle, XCircle, AlertCircle,
|
||||
BarChart3, PieChart, Activity, Shield
|
||||
} from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
||||
import { Badge } from '../ui/badge';
|
||||
import { Progress } from '../ui/progress';
|
||||
import { usePolkadot } from '../../contexts/PolkadotContext';
|
||||
import { formatBalance } from '../../lib/wallet';
|
||||
|
||||
interface GovernanceStats {
|
||||
activeProposals: number;
|
||||
activeElections: number;
|
||||
totalVoters: number;
|
||||
participationRate: number;
|
||||
parliamentMembers: number;
|
||||
diwanMembers: number;
|
||||
nextElection: string;
|
||||
treasuryBalance: string;
|
||||
}
|
||||
|
||||
const GovernanceOverview: React.FC = () => {
|
||||
const { api, isApiReady } = usePolkadot();
|
||||
const [stats, setStats] = useState<GovernanceStats>({
|
||||
activeProposals: 0,
|
||||
activeElections: 0,
|
||||
totalVoters: 0,
|
||||
participationRate: 0,
|
||||
parliamentMembers: 0,
|
||||
diwanMembers: 0,
|
||||
nextElection: '-',
|
||||
treasuryBalance: '0 HEZ'
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchGovernanceData = async () => {
|
||||
if (!api || !isApiReady) {
|
||||
console.log('API not ready for governance data');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('📊 Fetching governance data from blockchain...');
|
||||
setLoading(true);
|
||||
|
||||
// Fetch active referenda (proposals)
|
||||
let activeProposals = 0;
|
||||
try {
|
||||
const referendaCount = await api.query.referenda.referendumCount();
|
||||
console.log('Referenda count:', referendaCount.toNumber());
|
||||
activeProposals = referendaCount.toNumber();
|
||||
} catch (err) {
|
||||
console.warn('Failed to fetch referenda count:', err);
|
||||
}
|
||||
|
||||
// Fetch treasury balance
|
||||
let treasuryBalance = '0 HEZ';
|
||||
try {
|
||||
const treasuryAccount = await api.query.system.account(
|
||||
'5EYCAe5ijiYfyeZ2JJCGq56LmPyNRAKzpG4QkoQkkQNB5e6Z' // Treasury pallet address
|
||||
);
|
||||
const balance = treasuryAccount.data.free.toString();
|
||||
treasuryBalance = `${formatBalance(balance)} HEZ`;
|
||||
console.log('Treasury balance:', treasuryBalance);
|
||||
} catch (err) {
|
||||
console.warn('Failed to fetch treasury balance:', err);
|
||||
}
|
||||
|
||||
// Fetch council members
|
||||
let parliamentMembers = 0;
|
||||
try {
|
||||
const members = await api.query.council.members();
|
||||
parliamentMembers = members.length;
|
||||
console.log('Council members:', parliamentMembers);
|
||||
} catch (err) {
|
||||
console.warn('Failed to fetch council members:', err);
|
||||
}
|
||||
|
||||
// Update stats
|
||||
setStats({
|
||||
activeProposals,
|
||||
activeElections: 0, // Not implemented yet
|
||||
totalVoters: 0, // Will be calculated from conviction voting
|
||||
participationRate: 0,
|
||||
parliamentMembers,
|
||||
diwanMembers: 0, // Not implemented yet
|
||||
nextElection: '-',
|
||||
treasuryBalance
|
||||
});
|
||||
|
||||
console.log('✅ Governance data updated:', {
|
||||
activeProposals,
|
||||
parliamentMembers,
|
||||
treasuryBalance
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch governance data:', error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchGovernanceData();
|
||||
}, [api, isApiReady]);
|
||||
|
||||
const [recentActivity] = useState([
|
||||
{ type: 'proposal', action: 'New proposal submitted', title: 'Treasury Allocation Update', time: '2 hours ago' },
|
||||
{ type: 'vote', action: 'Vote cast', title: 'Infrastructure Development Fund', time: '3 hours ago' },
|
||||
{ type: 'election', action: 'Election started', title: 'Parliamentary Elections 2024', time: '1 day ago' },
|
||||
{ type: 'approved', action: 'Proposal approved', title: 'Community Grant Program', time: '2 days ago' }
|
||||
]);
|
||||
|
||||
const getActivityIcon = (type: string) => {
|
||||
switch(type) {
|
||||
case 'proposal': return <FileText className="w-4 h-4 text-blue-400" />;
|
||||
case 'vote': return <Vote className="w-4 h-4 text-purple-400" />;
|
||||
case 'election': return <Users className="w-4 h-4 text-cyan-400" />;
|
||||
case 'approved': return <CheckCircle className="w-4 h-4 text-green-400" />;
|
||||
default: return <Activity className="w-4 h-4 text-gray-400" />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-gray-400 text-sm">Active Proposals</p>
|
||||
<p className="text-2xl font-bold text-white mt-1">{stats.activeProposals}</p>
|
||||
<p className="text-xs text-green-400 mt-2">+3 this week</p>
|
||||
</div>
|
||||
<div className="p-3 bg-blue-500/10 rounded-lg">
|
||||
<FileText className="w-6 h-6 text-blue-400" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-gray-400 text-sm">Active Elections</p>
|
||||
<p className="text-2xl font-bold text-white mt-1">{stats.activeElections}</p>
|
||||
<p className="text-xs text-cyan-400 mt-2">Next in {stats.nextElection}</p>
|
||||
</div>
|
||||
<div className="p-3 bg-cyan-500/10 rounded-lg">
|
||||
<Users className="w-6 h-6 text-cyan-400" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-gray-400 text-sm">Participation Rate</p>
|
||||
<p className="text-2xl font-bold text-white mt-1">{stats.participationRate}%</p>
|
||||
<Progress value={stats.participationRate} className="mt-2 h-1" />
|
||||
</div>
|
||||
<div className="p-3 bg-kurdish-green/10 rounded-lg">
|
||||
<TrendingUpIcon className="w-6 h-6 text-kurdish-green" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-gray-400 text-sm">Treasury Balance</p>
|
||||
<p className="text-2xl font-bold text-white mt-1">{stats.treasuryBalance}</p>
|
||||
<p className="text-xs text-yellow-400 mt-2">Available for proposals</p>
|
||||
</div>
|
||||
<div className="p-3 bg-yellow-500/10 rounded-lg">
|
||||
<Shield className="w-6 h-6 text-yellow-400" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Government Bodies */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center">
|
||||
<Gavel className="w-5 h-5 mr-2 text-purple-400" />
|
||||
Parliament Status
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-400">Active Members</span>
|
||||
<span className="text-white font-semibold">{stats.parliamentMembers}/27</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-400">Current Session</span>
|
||||
<Badge className="bg-green-500/10 text-green-400 border-green-500/20">In Session</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-400">Pending Votes</span>
|
||||
<span className="text-white font-semibold">5</span>
|
||||
</div>
|
||||
<div className="pt-2 border-t border-gray-800">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-400">Quorum Status</span>
|
||||
<span className="text-green-400">Met (85%)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center">
|
||||
<Shield className="w-5 h-5 mr-2 text-cyan-400" />
|
||||
Dîwan (Constitutional Court)
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-400">Active Judges</span>
|
||||
<span className="text-white font-semibold">{stats.diwanMembers}/9</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-400">Pending Reviews</span>
|
||||
<span className="text-white font-semibold">3</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-400">Recent Decisions</span>
|
||||
<span className="text-white font-semibold">12</span>
|
||||
</div>
|
||||
<div className="pt-2 border-t border-gray-800">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-400">Next Hearing</span>
|
||||
<span className="text-cyan-400">Tomorrow, 14:00 UTC</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Recent Activity */}
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center">
|
||||
<Activity className="w-5 h-5 mr-2 text-purple-400" />
|
||||
Recent Governance Activity
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
{recentActivity.map((activity, index) => (
|
||||
<div key={index} className="flex items-start space-x-3 p-3 rounded-lg hover:bg-gray-800/50 transition-colors">
|
||||
{getActivityIcon(activity.type)}
|
||||
<div className="flex-1">
|
||||
<p className="text-sm text-gray-300">{activity.action}</p>
|
||||
<p className="text-xs text-white font-medium mt-1">{activity.title}</p>
|
||||
</div>
|
||||
<span className="text-xs text-gray-500">{activity.time}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Voting Power Distribution */}
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center">
|
||||
<PieChart className="w-5 h-5 mr-2 text-purple-400" />
|
||||
Voting Power Distribution
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-400">Direct Votes</span>
|
||||
<span className="text-white font-semibold">45%</span>
|
||||
</div>
|
||||
<Progress value={45} className="h-2 bg-gray-800" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-400">Delegated Votes</span>
|
||||
<span className="text-white font-semibold">35%</span>
|
||||
</div>
|
||||
<Progress value={35} className="h-2 bg-gray-800" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-400">Proxy Votes</span>
|
||||
<span className="text-white font-semibold">20%</span>
|
||||
</div>
|
||||
<Progress value={20} className="h-2 bg-gray-800" />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GovernanceOverview;
|
||||
@@ -0,0 +1,156 @@
|
||||
import React, { useState } from 'react';
|
||||
import { FileText, Vote, Clock, TrendingUp, Users, AlertCircle } from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
||||
import { Badge } from '../ui/badge';
|
||||
import { Button } from '../ui/button';
|
||||
import { Progress } from '../ui/progress';
|
||||
|
||||
interface Proposal {
|
||||
id: number;
|
||||
title: string;
|
||||
description: string;
|
||||
proposer: string;
|
||||
type: 'treasury' | 'executive' | 'constitutional' | 'simple';
|
||||
status: 'active' | 'passed' | 'rejected' | 'pending';
|
||||
ayeVotes: number;
|
||||
nayVotes: number;
|
||||
totalVotes: number;
|
||||
quorum: number;
|
||||
deadline: string;
|
||||
requestedAmount?: string;
|
||||
}
|
||||
|
||||
const ProposalsList: React.FC = () => {
|
||||
const [proposals] = useState<Proposal[]>([
|
||||
{
|
||||
id: 1,
|
||||
title: 'Treasury Allocation for Development Fund',
|
||||
description: 'Allocate 500,000 PEZ for ecosystem development',
|
||||
proposer: '5GrwvaEF...',
|
||||
type: 'treasury',
|
||||
status: 'active',
|
||||
ayeVotes: 156,
|
||||
nayVotes: 45,
|
||||
totalVotes: 201,
|
||||
quorum: 60,
|
||||
deadline: '2 days',
|
||||
requestedAmount: '500,000 PEZ'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Update Staking Parameters',
|
||||
description: 'Increase minimum stake requirement to 1000 HEZ',
|
||||
proposer: '5FHneW46...',
|
||||
type: 'executive',
|
||||
status: 'active',
|
||||
ayeVotes: 89,
|
||||
nayVotes: 112,
|
||||
totalVotes: 201,
|
||||
quorum: 60,
|
||||
deadline: '5 days'
|
||||
}
|
||||
]);
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
switch(status) {
|
||||
case 'active': return <Badge className="bg-blue-500/10 text-blue-400">Active</Badge>;
|
||||
case 'passed': return <Badge className="bg-green-500/10 text-green-400">Passed</Badge>;
|
||||
case 'rejected': return <Badge className="bg-red-500/10 text-red-400">Rejected</Badge>;
|
||||
default: return <Badge className="bg-gray-500/10 text-gray-400">Pending</Badge>;
|
||||
}
|
||||
};
|
||||
|
||||
const getTypeBadge = (type: string) => {
|
||||
switch(type) {
|
||||
case 'treasury': return <Badge className="bg-yellow-500/10 text-yellow-400">Treasury</Badge>;
|
||||
case 'executive': return <Badge className="bg-kurdish-red/10 text-kurdish-red">Executive</Badge>;
|
||||
case 'constitutional': return <Badge className="bg-cyan-500/10 text-cyan-400">Constitutional</Badge>;
|
||||
default: return <Badge className="bg-gray-500/10 text-gray-400">Simple</Badge>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{proposals.map((proposal) => {
|
||||
const ayePercentage = (proposal.ayeVotes / proposal.totalVotes) * 100;
|
||||
const nayPercentage = (proposal.nayVotes / proposal.totalVotes) * 100;
|
||||
const quorumReached = (proposal.totalVotes / 300) * 100 >= proposal.quorum;
|
||||
|
||||
return (
|
||||
<Card key={proposal.id} className="bg-gray-900/50 border-gray-800">
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-gray-400 text-sm">#{proposal.id}</span>
|
||||
{getTypeBadge(proposal.type)}
|
||||
{getStatusBadge(proposal.status)}
|
||||
</div>
|
||||
<CardTitle className="text-white text-lg">{proposal.title}</CardTitle>
|
||||
<p className="text-gray-400 text-sm">{proposal.description}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="flex items-center text-gray-400 text-sm">
|
||||
<Clock className="w-4 h-4 mr-1" />
|
||||
{proposal.deadline}
|
||||
</div>
|
||||
{proposal.requestedAmount && (
|
||||
<div className="mt-2 text-yellow-400 font-semibold">
|
||||
{proposal.requestedAmount}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-400">Voting Progress</span>
|
||||
<span className="text-white">{proposal.totalVotes} votes</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-green-400 text-xs w-12">Aye</span>
|
||||
<Progress value={ayePercentage} className="flex-1 h-2" />
|
||||
<span className="text-white text-sm w-12 text-right">{ayePercentage.toFixed(0)}%</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span className="text-red-400 text-xs w-12">Nay</span>
|
||||
<Progress value={nayPercentage} className="flex-1 h-2" />
|
||||
<span className="text-white text-sm w-12 text-right">{nayPercentage.toFixed(0)}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between pt-3 border-t border-gray-800">
|
||||
<div className="flex items-center space-x-4 text-sm">
|
||||
<div className="flex items-center">
|
||||
<Users className="w-4 h-4 mr-1 text-gray-400" />
|
||||
<span className="text-gray-400">Proposer: {proposal.proposer}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
{quorumReached ? (
|
||||
<span className="text-green-400">✓ Quorum reached</span>
|
||||
) : (
|
||||
<span className="text-yellow-400">⚠ Quorum: {proposal.quorum}%</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button size="sm" variant="outline" className="border-gray-700">
|
||||
View Details
|
||||
</Button>
|
||||
<Button size="sm" className="bg-kurdish-green hover:bg-kurdish-green/80">
|
||||
Cast Vote
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProposalsList;
|
||||
Reference in New Issue
Block a user