mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-28 01:28:00 +00:00
feat: complete i18n support for all components (6 languages)
Add full internationalization across 127+ components and pages. 790+ translation keys in en, tr, kmr, ckb, ar, fa locales. Remove duplicate keys and delete unused .json locale files.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -28,6 +29,7 @@ interface ElectionWithCandidates extends ElectionInfo {
|
||||
}
|
||||
|
||||
const ElectionsInterface: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { api, isApiReady } = usePezkuwi();
|
||||
const { account, signer } = useWallet();
|
||||
const [elections, setElections] = useState<ElectionWithCandidates[]>([]);
|
||||
@@ -101,7 +103,7 @@ const ElectionsInterface: React.FC = () => {
|
||||
|
||||
const handleVote = async (electionId: number, candidateAccount: string, electionType: string) => {
|
||||
if (!api || !account || !signer) {
|
||||
toast.error('Please connect your wallet first');
|
||||
toast.error(t('elections.connectWallet'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -124,35 +126,35 @@ const ElectionsInterface: React.FC = () => {
|
||||
|
||||
await tx.signAndSend(account, { signer }, ({ status, dispatchError }) => {
|
||||
if (status.isInBlock) {
|
||||
toast.success('Vote submitted successfully!');
|
||||
toast.success(t('elections.voteSuccess'));
|
||||
setVotingElectionId(null);
|
||||
}
|
||||
if (dispatchError) {
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
||||
toast.error(`Vote failed: ${decoded.name}`);
|
||||
toast.error(t('elections.voteFailed', { error: decoded.name }));
|
||||
} else {
|
||||
toast.error(`Vote failed: ${dispatchError.toString()}`);
|
||||
toast.error(t('elections.voteFailed', { error: dispatchError.toString() }));
|
||||
}
|
||||
setVotingElectionId(null);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error voting:', err);
|
||||
toast.error('Failed to submit vote');
|
||||
toast.error(t('elections.voteError'));
|
||||
setVotingElectionId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const submitParliamentaryVotes = async (electionId: number) => {
|
||||
if (!api || !account || !signer) {
|
||||
toast.error('Please connect your wallet first');
|
||||
toast.error(t('elections.connectWallet'));
|
||||
return;
|
||||
}
|
||||
|
||||
const candidates = selectedCandidates.get(electionId) || [];
|
||||
if (candidates.length === 0) {
|
||||
toast.error('Please select at least one candidate');
|
||||
toast.error(t('elections.selectCandidate'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -163,34 +165,34 @@ const ElectionsInterface: React.FC = () => {
|
||||
|
||||
await tx.signAndSend(account, { signer }, ({ status, dispatchError }) => {
|
||||
if (status.isInBlock) {
|
||||
toast.success('Votes submitted successfully!');
|
||||
toast.success(t('elections.votesSuccess'));
|
||||
setSelectedCandidates(new Map(selectedCandidates.set(electionId, [])));
|
||||
setVotingElectionId(null);
|
||||
}
|
||||
if (dispatchError) {
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
||||
toast.error(`Vote failed: ${decoded.name}`);
|
||||
toast.error(t('elections.voteFailed', { error: decoded.name }));
|
||||
} else {
|
||||
toast.error(`Vote failed: ${dispatchError.toString()}`);
|
||||
toast.error(t('elections.voteFailed', { error: dispatchError.toString() }));
|
||||
}
|
||||
setVotingElectionId(null);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error voting:', err);
|
||||
toast.error('Failed to submit votes');
|
||||
toast.error(t('elections.votesError'));
|
||||
setVotingElectionId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const formatRemainingTime = (endBlock: number) => {
|
||||
const remaining = endBlock - currentBlock;
|
||||
if (remaining <= 0) return 'Ended';
|
||||
if (remaining <= 0) return t('elections.ended');
|
||||
const time = blocksToTime(remaining);
|
||||
if (time.days > 0) return `${time.days}d ${time.hours}h remaining`;
|
||||
if (time.hours > 0) return `${time.hours}h ${time.minutes}m remaining`;
|
||||
return `${time.minutes}m remaining`;
|
||||
if (time.days > 0) return t('elections.daysRemaining', { days: time.days, hours: time.hours });
|
||||
if (time.hours > 0) return t('elections.hoursRemaining', { hours: time.hours, minutes: time.minutes });
|
||||
return t('elections.minutesRemaining', { minutes: time.minutes });
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
@@ -206,7 +208,7 @@ const ElectionsInterface: React.FC = () => {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-green-500" />
|
||||
<span className="ml-3 text-gray-400">Loading elections from blockchain...</span>
|
||||
<span className="ml-3 text-gray-400">{t('elections.loading')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -217,7 +219,7 @@ const ElectionsInterface: React.FC = () => {
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center text-red-400">
|
||||
<AlertCircle className="w-5 h-5 mr-2" />
|
||||
Error loading elections: {error}
|
||||
{t('elections.loadError', { error })}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -231,17 +233,17 @@ const ElectionsInterface: React.FC = () => {
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline" className="bg-green-500/10 border-green-500 text-green-400">
|
||||
<Activity className="h-3 w-3 mr-1" />
|
||||
Live Blockchain Data
|
||||
{t('elections.liveData')}
|
||||
</Badge>
|
||||
<span className="text-sm text-gray-500">Block #{currentBlock.toLocaleString()}</span>
|
||||
<span className="text-sm text-gray-500">{t('elections.block', { number: currentBlock.toLocaleString() })}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="active" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-3 bg-gray-800/50">
|
||||
<TabsTrigger value="active">Active Elections</TabsTrigger>
|
||||
<TabsTrigger value="register">Register</TabsTrigger>
|
||||
<TabsTrigger value="results">Results</TabsTrigger>
|
||||
<TabsTrigger value="active">{t('elections.activeElections')}</TabsTrigger>
|
||||
<TabsTrigger value="register">{t('elections.register')}</TabsTrigger>
|
||||
<TabsTrigger value="results">{t('elections.results')}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="active" className="space-y-4">
|
||||
@@ -249,8 +251,8 @@ const ElectionsInterface: React.FC = () => {
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardContent className="pt-6 text-center text-gray-400">
|
||||
<Users className="w-12 h-12 mx-auto mb-4 opacity-50" />
|
||||
<p>No active elections at this time</p>
|
||||
<p className="text-sm mt-2">Check back later for upcoming elections</p>
|
||||
<p>{t('elections.noActive')}</p>
|
||||
<p className="text-sm mt-2">{t('elections.checkBack')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
@@ -276,7 +278,7 @@ const ElectionsInterface: React.FC = () => {
|
||||
{election.userHasVoted && (
|
||||
<Badge variant="outline" className="border-green-500 text-green-400">
|
||||
<CheckCircle className="w-3 h-3 mr-1" />
|
||||
You Voted
|
||||
{t('elections.youVoted')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
@@ -286,7 +288,7 @@ const ElectionsInterface: React.FC = () => {
|
||||
{election.status === 'VotingPeriod' && (
|
||||
<div className="space-y-4">
|
||||
{election.candidates.length === 0 ? (
|
||||
<p className="text-gray-400 text-center py-4">No candidates registered</p>
|
||||
<p className="text-gray-400 text-center py-4">{t('elections.noCandidates')}</p>
|
||||
) : (
|
||||
<>
|
||||
{election.candidates.map(candidate => {
|
||||
@@ -304,13 +306,13 @@ const ElectionsInterface: React.FC = () => {
|
||||
{candidate.account.substring(0, 8)}...{candidate.account.slice(-6)}
|
||||
</p>
|
||||
<p className="text-sm text-gray-400">
|
||||
{candidate.endorsersCount} endorsements
|
||||
{t('elections.endorsements', { count: candidate.endorsersCount })}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="font-bold text-white">{percentage.toFixed(1)}%</p>
|
||||
<p className="text-sm text-gray-400">
|
||||
{candidate.voteCount.toLocaleString()} votes
|
||||
{t('elections.votesCount', { count: candidate.voteCount.toLocaleString() })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -326,17 +328,17 @@ const ElectionsInterface: React.FC = () => {
|
||||
{votingElectionId === election.electionId ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Submitting...
|
||||
{t('elections.submitting')}
|
||||
</>
|
||||
) : isSelected ? (
|
||||
<>
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
Selected
|
||||
{t('elections.selected')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Vote className="w-4 h-4 mr-2" />
|
||||
{election.electionType === 'Parliamentary' ? 'Select' : 'Vote'}
|
||||
{election.electionType === 'Parliamentary' ? t('elections.select') : t('elections.vote')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -348,7 +350,7 @@ const ElectionsInterface: React.FC = () => {
|
||||
{election.electionType === 'Parliamentary' && !election.userHasVoted && (
|
||||
<div className="mt-4 pt-4 border-t border-gray-700">
|
||||
<p className="text-sm text-gray-400 text-center mb-3">
|
||||
Select multiple candidates for parliamentary election
|
||||
{t('elections.selectMultiple')}
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => submitParliamentaryVotes(election.electionId)}
|
||||
@@ -359,12 +361,12 @@ const ElectionsInterface: React.FC = () => {
|
||||
{votingElectionId === election.electionId ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Submitting Votes...
|
||||
{t('elections.submittingVotes')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Vote className="w-4 h-4 mr-2" />
|
||||
Submit {(selectedCandidates.get(election.electionId) || []).length} Vote(s)
|
||||
{t('elections.submitVotes', { count: (selectedCandidates.get(election.electionId) || []).length })}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -378,17 +380,17 @@ const ElectionsInterface: React.FC = () => {
|
||||
{election.status === 'CandidacyPeriod' && (
|
||||
<div className="text-center py-4">
|
||||
<p className="text-gray-400 mb-4">
|
||||
{election.totalCandidates} candidates registered so far
|
||||
{t('elections.candidatesRegistered', { count: election.totalCandidates })}
|
||||
</p>
|
||||
<Button variant="outline">
|
||||
Register as Candidate
|
||||
{t('elections.registerAsCandidate')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{election.status === 'CampaignPeriod' && (
|
||||
<div className="text-center py-4 text-gray-400">
|
||||
<p>{election.totalCandidates} candidates competing</p>
|
||||
<p>{t('elections.candidatesCompeting', { count: election.totalCandidates })}</p>
|
||||
<p className="text-sm mt-2">Voting begins {formatRemainingTime(election.campaignEndBlock)}</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -401,9 +403,9 @@ const ElectionsInterface: React.FC = () => {
|
||||
<TabsContent value="register">
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white">Candidate Registration</CardTitle>
|
||||
<CardTitle className="text-white">{t('elections.candidateRegistration')}</CardTitle>
|
||||
<CardDescription>
|
||||
Register as a candidate for upcoming elections
|
||||
{t('elections.registerDescription')}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
@@ -412,7 +414,7 @@ const ElectionsInterface: React.FC = () => {
|
||||
<AlertCircle className="w-5 h-5 text-amber-500 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-medium text-amber-400">
|
||||
Requirements
|
||||
{t('elections.requirements')}
|
||||
</p>
|
||||
<ul className="text-sm text-amber-300/80 mt-2 space-y-1">
|
||||
<li>• Minimum Trust Score: 300 (Parliamentary) / 600 (Presidential)</li>
|
||||
@@ -424,7 +426,7 @@ const ElectionsInterface: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
<Button className="w-full bg-green-600 hover:bg-green-700" size="lg">
|
||||
Register as Candidate
|
||||
{t('elections.registerAsCandidate')}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -433,14 +435,14 @@ const ElectionsInterface: React.FC = () => {
|
||||
<TabsContent value="results">
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white">Election Results</CardTitle>
|
||||
<CardDescription>Historical election outcomes</CardDescription>
|
||||
<CardTitle className="text-white">{t('elections.electionResults')}</CardTitle>
|
||||
<CardDescription>{t('elections.resultsDescription')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{completedResults.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-400">
|
||||
<Trophy className="w-12 h-12 mx-auto mb-4 opacity-50" />
|
||||
<p>No completed elections yet</p>
|
||||
<p>{t('elections.noCompletedElections')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
@@ -448,36 +450,36 @@ const ElectionsInterface: React.FC = () => {
|
||||
<div key={result.electionId} className="p-4 border border-gray-700 rounded-lg">
|
||||
<div className="flex justify-between items-start mb-3">
|
||||
<div>
|
||||
<p className="font-medium text-white">Election #{result.electionId}</p>
|
||||
<p className="font-medium text-white">{t('governance.historyTab.election', { id: result.electionId })}</p>
|
||||
<p className="text-sm text-gray-400">
|
||||
Finalized at block #{result.finalizedAt.toLocaleString()}
|
||||
{t('elections.finalizedAtBlock', { block: result.finalizedAt.toLocaleString() })}
|
||||
</p>
|
||||
</div>
|
||||
<Badge variant="outline" className="border-green-500 text-green-400">
|
||||
<Trophy className="w-3 h-3 mr-1" />
|
||||
Completed
|
||||
{t('elections.completed')}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-400">Winner(s)</span>
|
||||
<span className="text-gray-400">{t('elections.winners')}</span>
|
||||
<span className="font-medium text-white">
|
||||
{result.winners.length > 0
|
||||
? result.winners.map(w => `${w.substring(0, 8)}...`).join(', ')
|
||||
: 'N/A'}
|
||||
: t('elections.na')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-400">Total Votes</span>
|
||||
<span className="text-gray-400">{t('elections.totalVotes')}</span>
|
||||
<span className="text-white">{result.totalVotes.toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-gray-400">Turnout</span>
|
||||
<span className="text-gray-400">{t('elections.turnout')}</span>
|
||||
<span className="text-white">{result.turnoutPercentage}%</span>
|
||||
</div>
|
||||
{result.runoffRequired && (
|
||||
<Badge className="bg-yellow-500/20 text-yellow-400">
|
||||
Runoff Required
|
||||
{t('elections.runoffRequired')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
@@ -25,6 +26,7 @@ interface CompletedProposal {
|
||||
}
|
||||
|
||||
const GovernanceHistory: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { api, isApiReady } = usePezkuwi();
|
||||
const [completedElections, setCompletedElections] = useState<ElectionResult[]>([]);
|
||||
const [completedProposals, setCompletedProposals] = useState<CompletedProposal[]>([]);
|
||||
@@ -163,7 +165,7 @@ const GovernanceHistory: React.FC = () => {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-green-500" />
|
||||
<span className="ml-3 text-gray-400">Loading governance history...</span>
|
||||
<span className="ml-3 text-gray-400">{t('governance.historyTab.loading')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -174,7 +176,7 @@ const GovernanceHistory: React.FC = () => {
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center text-red-400">
|
||||
<XCircle className="w-5 h-5 mr-2" />
|
||||
Error: {error}
|
||||
{t('myVotes.error', { error })}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -192,7 +194,7 @@ const GovernanceHistory: React.FC = () => {
|
||||
<Trophy className="w-8 h-8 text-yellow-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-white">{stats.totalElectionsHeld}</div>
|
||||
<div className="text-sm text-gray-400">Elections Held</div>
|
||||
<div className="text-sm text-gray-400">{t('governance.historyTab.electionsHeld')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -203,7 +205,7 @@ const GovernanceHistory: React.FC = () => {
|
||||
<FileText className="w-8 h-8 text-purple-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-white">{stats.totalProposalsSubmitted}</div>
|
||||
<div className="text-sm text-gray-400">Total Proposals</div>
|
||||
<div className="text-sm text-gray-400">{t('governance.historyTab.totalProposals')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -214,7 +216,7 @@ const GovernanceHistory: React.FC = () => {
|
||||
<Users className="w-8 h-8 text-cyan-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-white">{stats.parliamentSize}</div>
|
||||
<div className="text-sm text-gray-400">Parliament Size</div>
|
||||
<div className="text-sm text-gray-400">{t('governance.historyTab.parliamentSize')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -225,7 +227,7 @@ const GovernanceHistory: React.FC = () => {
|
||||
<TrendingUp className="w-8 h-8 text-green-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-white">{stats.averageTurnout}%</div>
|
||||
<div className="text-sm text-gray-400">Avg Turnout</div>
|
||||
<div className="text-sm text-gray-400">{t('governance.historyTab.avgTurnout')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -237,15 +239,15 @@ const GovernanceHistory: React.FC = () => {
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline" className="bg-green-500/10 border-green-500 text-green-400">
|
||||
<Activity className="h-3 w-3 mr-1" />
|
||||
Live Blockchain Data
|
||||
{t('governance.historyTab.liveData')}
|
||||
</Badge>
|
||||
<span className="text-sm text-gray-500">Block #{currentBlock.toLocaleString()}</span>
|
||||
<span className="text-sm text-gray-500">{t('governance.historyTab.block', { number: currentBlock.toLocaleString() })}</span>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="elections" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-2 bg-gray-800/50">
|
||||
<TabsTrigger value="elections">Election History</TabsTrigger>
|
||||
<TabsTrigger value="proposals">Proposal History</TabsTrigger>
|
||||
<TabsTrigger value="elections">{t('governance.historyTab.electionHistory')}</TabsTrigger>
|
||||
<TabsTrigger value="proposals">{t('governance.historyTab.proposalHistory')}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="elections" className="space-y-4">
|
||||
@@ -253,8 +255,8 @@ const GovernanceHistory: React.FC = () => {
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardContent className="pt-6 text-center text-gray-400">
|
||||
<Trophy className="w-12 h-12 mx-auto mb-4 opacity-50" />
|
||||
<p>No completed elections in history</p>
|
||||
<p className="text-sm mt-2">Election results will appear here once voting concludes</p>
|
||||
<p>{t('governance.historyTab.noElections')}</p>
|
||||
<p className="text-sm mt-2">{t('governance.historyTab.electionsWillAppear')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
@@ -265,21 +267,21 @@ const GovernanceHistory: React.FC = () => {
|
||||
<div>
|
||||
<h4 className="font-medium text-white flex items-center gap-2">
|
||||
<Trophy className="w-4 h-4 text-yellow-500" />
|
||||
Election #{election.electionId}
|
||||
{t('governance.historyTab.election', { id: election.electionId })}
|
||||
</h4>
|
||||
<p className="text-sm text-gray-400 mt-1 flex items-center gap-1">
|
||||
<Calendar className="w-3 h-3" />
|
||||
Finalized {formatBlockTime(election.finalizedAt)}
|
||||
{t('governance.historyTab.finalized', { time: formatBlockTime(election.finalizedAt) })}
|
||||
</p>
|
||||
</div>
|
||||
<Badge className="bg-green-500/20 text-green-400">
|
||||
Completed
|
||||
{t('governance.historyTab.completed')}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-gray-400">Winner(s):</span>
|
||||
<span className="text-gray-400">{t('governance.historyTab.winners')}</span>
|
||||
<div className="mt-1 space-y-1">
|
||||
{election.winners.length > 0 ? (
|
||||
election.winners.map((winner, idx) => (
|
||||
@@ -288,24 +290,24 @@ const GovernanceHistory: React.FC = () => {
|
||||
</Badge>
|
||||
))
|
||||
) : (
|
||||
<span className="text-gray-500">No winners</span>
|
||||
<span className="text-gray-500">{t('governance.historyTab.noWinners')}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-gray-400">Total Votes</div>
|
||||
<div className="text-gray-400">{t('governance.historyTab.totalVotes')}</div>
|
||||
<div className="text-white font-medium">{election.totalVotes.toLocaleString()}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 pt-4 border-t border-gray-700 flex justify-between text-sm">
|
||||
<div>
|
||||
<span className="text-gray-400">Turnout: </span>
|
||||
<span className="text-gray-400">{t('governance.historyTab.turnout')} </span>
|
||||
<span className="text-white">{election.turnoutPercentage}%</span>
|
||||
</div>
|
||||
{election.runoffRequired && (
|
||||
<Badge className="bg-yellow-500/20 text-yellow-400">
|
||||
Runoff Required
|
||||
{t('governance.historyTab.runoffRequired')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
@@ -320,8 +322,8 @@ const GovernanceHistory: React.FC = () => {
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardContent className="pt-6 text-center text-gray-400">
|
||||
<FileText className="w-12 h-12 mx-auto mb-4 opacity-50" />
|
||||
<p>No completed proposals in history</p>
|
||||
<p className="text-sm mt-2">Proposal outcomes will appear here once voting concludes</p>
|
||||
<p>{t('governance.historyTab.noProposals')}</p>
|
||||
<p className="text-sm mt-2">{t('governance.historyTab.proposalsWillAppear')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
@@ -334,16 +336,16 @@ const GovernanceHistory: React.FC = () => {
|
||||
<div>
|
||||
<h4 className="font-medium text-white">{proposal.title}</h4>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
Proposed by {proposal.proposer.substring(0, 8)}...
|
||||
{t('governance.historyTab.proposedBy', { address: `${proposal.proposer.substring(0, 8)}...` })}
|
||||
</p>
|
||||
<div className="flex items-center gap-4 mt-2 text-sm">
|
||||
<span className="text-green-400">
|
||||
<CheckCircle className="w-3 h-3 inline mr-1" />
|
||||
{proposal.ayeVotes} Aye
|
||||
{proposal.ayeVotes} {t('governance.historyTab.aye')}
|
||||
</span>
|
||||
<span className="text-red-400">
|
||||
<XCircle className="w-3 h-3 inline mr-1" />
|
||||
{proposal.nayVotes} Nay
|
||||
{proposal.nayVotes} {t('governance.historyTab.nay')}
|
||||
</span>
|
||||
<span className="text-gray-500">
|
||||
{formatBlockTime(proposal.finalizedAt)}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Users, Gavel, FileText, TrendingUpIcon,
|
||||
Shield
|
||||
@@ -30,6 +31,7 @@ const RELAY_TREASURY = '5EYCAe5ijiYfyeZ2JJCGq56LmPyNRAKzpG4QkoQkkQNB5e6Z'; // py
|
||||
const PEZ_TREASURY = '5EYCAe5iipewaoUvoNr8ttcKqj5czZPBvVAex6uWbT6HxQNU'; // pez/trea (Asset Hub)
|
||||
|
||||
const GovernanceOverview: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { api, isApiReady, assetHubApi, isAssetHubReady } = usePezkuwi();
|
||||
const [stats, setStats] = useState<GovernanceStats>({
|
||||
activeProposals: 0,
|
||||
@@ -165,7 +167,7 @@ const GovernanceOverview: React.FC = () => {
|
||||
}, [api, isApiReady, assetHubApi, isAssetHubReady]);
|
||||
|
||||
if (loading) {
|
||||
return <LoadingState message="Loading governance data..." />;
|
||||
return <LoadingState message={t('governance.overview.loading')} />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -176,7 +178,7 @@ const GovernanceOverview: React.FC = () => {
|
||||
<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-gray-400 text-sm">{t('governance.overview.activeProposals')}</p>
|
||||
<p className="text-2xl font-bold text-white mt-1">{stats.activeProposals}</p>
|
||||
</div>
|
||||
<div className="p-3 bg-blue-500/10 rounded-lg">
|
||||
@@ -190,7 +192,7 @@ const GovernanceOverview: React.FC = () => {
|
||||
<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-gray-400 text-sm">{t('governance.overview.activeElections')}</p>
|
||||
<p className="text-2xl font-bold text-white mt-1">{stats.activeElections}</p>
|
||||
</div>
|
||||
<div className="p-3 bg-cyan-500/10 rounded-lg">
|
||||
@@ -204,7 +206,7 @@ const GovernanceOverview: React.FC = () => {
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-gray-400 text-sm">Total Voters</p>
|
||||
<p className="text-gray-400 text-sm">{t('governance.overview.totalVoters')}</p>
|
||||
<p className="text-2xl font-bold text-white mt-1">{stats.totalVoters}</p>
|
||||
</div>
|
||||
<div className="p-3 bg-kurdish-green/10 rounded-lg">
|
||||
@@ -218,7 +220,7 @@ const GovernanceOverview: React.FC = () => {
|
||||
<CardContent className="p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-gray-400 text-sm">Treasury</p>
|
||||
<p className="text-gray-400 text-sm">{t('governance.overview.treasury')}</p>
|
||||
<p className="text-lg font-bold text-white mt-1">{stats.pezTreasuryBalance}</p>
|
||||
<p className="text-sm text-gray-400 mt-1">{stats.relayTreasuryBalance}</p>
|
||||
</div>
|
||||
@@ -236,23 +238,23 @@ const GovernanceOverview: React.FC = () => {
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center">
|
||||
<Gavel className="w-5 h-5 mr-2 text-purple-400" />
|
||||
Parliament Status
|
||||
{t('governance.overview.parliamentStatus')}
|
||||
</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-gray-400">{t('governance.overview.activeMembers')}</span>
|
||||
<span className="text-white font-semibold">{stats.parliamentMembers}/{stats.parliamentMax}</span>
|
||||
</div>
|
||||
{stats.activeElections > 0 && (
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-400">Elections</span>
|
||||
<Badge className="bg-green-500/10 text-green-400 border-green-500/20">Active</Badge>
|
||||
<span className="text-gray-400">{t('governance.overview.elections')}</span>
|
||||
<Badge className="bg-green-500/10 text-green-400 border-green-500/20">{t('governance.status.active')}</Badge>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-400">Pending Votes</span>
|
||||
<span className="text-gray-400">{t('governance.overview.pendingVotes')}</span>
|
||||
<span className="text-white font-semibold">{stats.pendingVotes}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -263,17 +265,17 @@ const GovernanceOverview: React.FC = () => {
|
||||
<CardHeader>
|
||||
<CardTitle className="text-white flex items-center">
|
||||
<Shield className="w-5 h-5 mr-2 text-cyan-400" />
|
||||
Diwan (Constitutional Court)
|
||||
{t('governance.overview.diwan')}
|
||||
</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-gray-400">{t('governance.overview.activeJudges')}</span>
|
||||
<span className="text-white font-semibold">{stats.diwanMembers}/{stats.diwanMax}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-gray-400">Pending Reviews</span>
|
||||
<span className="text-gray-400">{t('governance.overview.pendingReviews')}</span>
|
||||
<span className="text-white font-semibold">{stats.diwanPendingReviews}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -35,6 +36,7 @@ interface DelegationInfo {
|
||||
}
|
||||
|
||||
const MyVotes: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { api, isApiReady } = usePezkuwi();
|
||||
const { account, isConnected } = useWallet();
|
||||
const [proposalVotes, setProposalVotes] = useState<ProposalVote[]>([]);
|
||||
@@ -195,12 +197,12 @@ const MyVotes: React.FC = () => {
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-center py-12">
|
||||
<Wallet className="w-16 h-16 mx-auto mb-4 text-gray-600" />
|
||||
<h3 className="text-xl font-semibold text-white mb-2">Connect Your Wallet</h3>
|
||||
<h3 className="text-xl font-semibold text-white mb-2">{t('myVotes.connectTitle')}</h3>
|
||||
<p className="text-gray-400 mb-6">
|
||||
Connect your wallet to view your voting history and delegations
|
||||
{t('myVotes.connectDescription')}
|
||||
</p>
|
||||
<Button className="bg-green-600 hover:bg-green-700">
|
||||
Connect Wallet
|
||||
{t('myVotes.connectButton')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -212,7 +214,7 @@ const MyVotes: React.FC = () => {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-green-500" />
|
||||
<span className="ml-3 text-gray-400">Loading your voting history...</span>
|
||||
<span className="ml-3 text-gray-400">{t('myVotes.loading')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -223,7 +225,7 @@ const MyVotes: React.FC = () => {
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center text-red-400">
|
||||
<XCircle className="w-5 h-5 mr-2" />
|
||||
Error: {error}
|
||||
{t('myVotes.error', { error })}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -244,7 +246,7 @@ const MyVotes: React.FC = () => {
|
||||
<Vote className="w-8 h-8 text-green-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-white">{totalVotes}</div>
|
||||
<div className="text-sm text-gray-400">Total Votes</div>
|
||||
<div className="text-sm text-gray-400">{t('myVotes.totalVotes')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -255,7 +257,7 @@ const MyVotes: React.FC = () => {
|
||||
<Activity className="w-8 h-8 text-blue-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-white">{activeVotes}</div>
|
||||
<div className="text-sm text-gray-400">Active Votes</div>
|
||||
<div className="text-sm text-gray-400">{t('myVotes.activeVotes')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -266,7 +268,7 @@ const MyVotes: React.FC = () => {
|
||||
<FileText className="w-8 h-8 text-purple-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-white">{proposalVotes.length}</div>
|
||||
<div className="text-sm text-gray-400">Proposal Votes</div>
|
||||
<div className="text-sm text-gray-400">{t('myVotes.proposalVotes')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -277,7 +279,7 @@ const MyVotes: React.FC = () => {
|
||||
<Users className="w-8 h-8 text-cyan-500" />
|
||||
<div>
|
||||
<div className="text-2xl font-bold text-white">{electionVotes.length}</div>
|
||||
<div className="text-sm text-gray-400">Election Votes</div>
|
||||
<div className="text-sm text-gray-400">{t('myVotes.electionVotes')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -288,18 +290,18 @@ const MyVotes: React.FC = () => {
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline" className="bg-green-500/10 border-green-500 text-green-400">
|
||||
<Activity className="h-3 w-3 mr-1" />
|
||||
Live Blockchain Data
|
||||
{t('myVotes.liveData')}
|
||||
</Badge>
|
||||
<span className="text-sm text-gray-500">
|
||||
Connected as {account?.substring(0, 8)}...{account?.slice(-6)}
|
||||
{t('myVotes.connectedAs', { address: `${account?.substring(0, 8)}...${account?.slice(-6)}` })}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="proposals" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-3 bg-gray-800/50">
|
||||
<TabsTrigger value="proposals">Proposal Votes</TabsTrigger>
|
||||
<TabsTrigger value="elections">Election Votes</TabsTrigger>
|
||||
<TabsTrigger value="delegations">My Delegations</TabsTrigger>
|
||||
<TabsTrigger value="proposals">{t('myVotes.proposalVotesTab')}</TabsTrigger>
|
||||
<TabsTrigger value="elections">{t('myVotes.electionVotesTab')}</TabsTrigger>
|
||||
<TabsTrigger value="delegations">{t('myVotes.delegationsTab')}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="proposals" className="space-y-4">
|
||||
@@ -307,8 +309,8 @@ const MyVotes: React.FC = () => {
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardContent className="pt-6 text-center text-gray-400">
|
||||
<FileText className="w-12 h-12 mx-auto mb-4 opacity-50" />
|
||||
<p>You have not voted on any proposals yet</p>
|
||||
<p className="text-sm mt-2">Check the Proposals tab to participate in governance</p>
|
||||
<p>{t('myVotes.noProposalVotes')}</p>
|
||||
<p className="text-sm mt-2">{t('myVotes.checkProposals')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
@@ -321,8 +323,7 @@ const MyVotes: React.FC = () => {
|
||||
<div>
|
||||
<h4 className="font-medium text-white">{vote.proposalTitle}</h4>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
Conviction: {vote.conviction}x •
|
||||
Amount: {formatTokenAmount(vote.amount)} HEZ
|
||||
{t('myVotes.conviction', { conviction: vote.conviction, amount: formatTokenAmount(vote.amount) })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -346,8 +347,8 @@ const MyVotes: React.FC = () => {
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardContent className="pt-6 text-center text-gray-400">
|
||||
<Users className="w-12 h-12 mx-auto mb-4 opacity-50" />
|
||||
<p>You have not voted in any elections yet</p>
|
||||
<p className="text-sm mt-2">Check the Elections tab to participate</p>
|
||||
<p>{t('myVotes.noElectionVotes')}</p>
|
||||
<p className="text-sm mt-2">{t('myVotes.checkElections')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
@@ -356,10 +357,9 @@ const MyVotes: React.FC = () => {
|
||||
<CardContent className="p-4">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<h4 className="font-medium text-white">{vote.electionType} Election</h4>
|
||||
<h4 className="font-medium text-white">{t('myVotes.electionName', { type: vote.electionType })}</h4>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
Election #{vote.electionId} •
|
||||
{vote.candidates.length} candidate(s) selected
|
||||
{t('myVotes.electionDetails', { id: vote.electionId, count: vote.candidates.length })}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{vote.candidates.map((candidate, idx) => (
|
||||
@@ -384,8 +384,8 @@ const MyVotes: React.FC = () => {
|
||||
<Card className="bg-gray-900/50 border-gray-800">
|
||||
<CardContent className="pt-6 text-center text-gray-400">
|
||||
<Users className="w-12 h-12 mx-auto mb-4 opacity-50" />
|
||||
<p>You have not delegated your voting power</p>
|
||||
<p className="text-sm mt-2">Check the Delegation tab to delegate your votes</p>
|
||||
<p>{t('myVotes.noDelegations')}</p>
|
||||
<p className="text-sm mt-2">{t('myVotes.checkDelegation')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
@@ -395,11 +395,10 @@ const MyVotes: React.FC = () => {
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<h4 className="font-medium text-white">
|
||||
Delegated to {delegation.delegateAddress.substring(0, 8)}...{delegation.delegateAddress.slice(-6)}
|
||||
{t('myVotes.delegatedTo', { address: `${delegation.delegateAddress.substring(0, 8)}...${delegation.delegateAddress.slice(-6)}` })}
|
||||
</h4>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
Amount: {formatTokenAmount(delegation.amount)} HEZ •
|
||||
Conviction: {delegation.conviction}x
|
||||
{t('myVotes.delegationDetails', { amount: formatTokenAmount(delegation.amount), conviction: delegation.conviction })}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{delegation.tracks.map((track, tidx) => (
|
||||
@@ -416,7 +415,7 @@ const MyVotes: React.FC = () => {
|
||||
{delegation.status}
|
||||
</Badge>
|
||||
<Button size="sm" variant="outline" className="text-xs">
|
||||
Revoke
|
||||
{t('myVotes.revoke')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Clock, Users, AlertCircle, Activity } from 'lucide-react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
||||
import { Badge } from '../ui/badge';
|
||||
@@ -24,6 +25,7 @@ interface Proposal {
|
||||
}
|
||||
|
||||
const ProposalsList: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { proposals: treasuryProposals, referenda, loading, error } = useGovernance();
|
||||
|
||||
// Format token amounts from blockchain units (12 decimals for HEZ)
|
||||
@@ -37,8 +39,8 @@ const ProposalsList: React.FC = () => {
|
||||
// Treasury proposals
|
||||
...treasuryProposals.map(p => ({
|
||||
id: p.proposalIndex,
|
||||
title: `Treasury Proposal #${p.proposalIndex}`,
|
||||
description: `Requesting ${formatTokenAmount(p.value)} HEZ for ${p.beneficiary.substring(0, 10)}...`,
|
||||
title: t('proposals.treasuryProposal', { id: p.proposalIndex }),
|
||||
description: t('proposals.treasuryDescription', { amount: formatTokenAmount(p.value), beneficiary: `${p.beneficiary.substring(0, 10)}...` }),
|
||||
proposer: p.proposer,
|
||||
type: 'treasury' as const,
|
||||
status: p.status as 'active' | 'passed' | 'rejected' | 'pending',
|
||||
@@ -46,14 +48,14 @@ const ProposalsList: React.FC = () => {
|
||||
nayVotes: 0,
|
||||
totalVotes: 0,
|
||||
quorum: 0,
|
||||
deadline: 'Pending referendum',
|
||||
deadline: t('proposals.pendingReferendum'),
|
||||
requestedAmount: `${formatTokenAmount(p.value)} HEZ`
|
||||
})),
|
||||
// Democracy referenda
|
||||
...referenda.map(r => ({
|
||||
id: r.index,
|
||||
title: `Referendum #${r.index}`,
|
||||
description: `Voting on proposal with ${r.threshold} threshold`,
|
||||
title: t('proposals.referendum', { id: r.index }),
|
||||
description: t('proposals.referendumDescription', { threshold: r.threshold }),
|
||||
proposer: 'Democracy',
|
||||
type: 'executive' as const,
|
||||
status: r.status as 'active' | 'passed' | 'rejected' | 'pending',
|
||||
@@ -67,24 +69,24 @@ const ProposalsList: React.FC = () => {
|
||||
|
||||
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>;
|
||||
case 'active': return <Badge className="bg-blue-500/10 text-blue-400">{t('governance.status.active')}</Badge>;
|
||||
case 'passed': return <Badge className="bg-green-500/10 text-green-400">{t('governance.status.passed')}</Badge>;
|
||||
case 'rejected': return <Badge className="bg-red-500/10 text-red-400">{t('governance.status.rejected')}</Badge>;
|
||||
default: return <Badge className="bg-gray-500/10 text-gray-400">{t('governance.status.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>;
|
||||
case 'treasury': return <Badge className="bg-yellow-500/10 text-yellow-400">{t('proposals.type.treasury')}</Badge>;
|
||||
case 'executive': return <Badge className="bg-kurdish-red/10 text-kurdish-red">{t('proposals.type.executive')}</Badge>;
|
||||
case 'constitutional': return <Badge className="bg-cyan-500/10 text-cyan-400">{t('proposals.type.constitutional')}</Badge>;
|
||||
default: return <Badge className="bg-gray-500/10 text-gray-400">{t('proposals.type.simple')}</Badge>;
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <LoadingState message="Loading proposals from blockchain..." />;
|
||||
return <LoadingState message={t('proposals.loading')} />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
@@ -92,7 +94,7 @@ const ProposalsList: React.FC = () => {
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
Failed to load proposals: {error}
|
||||
{t('proposals.loadError', { error })}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
@@ -104,17 +106,17 @@ const ProposalsList: React.FC = () => {
|
||||
<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
|
||||
{t('proposals.liveData')}
|
||||
</Badge>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{proposals.length} active proposals & referenda
|
||||
{t('proposals.count', { count: proposals.length })}
|
||||
</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.
|
||||
{t('proposals.noProposals')}
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
@@ -152,17 +154,17 @@ const ProposalsList: React.FC = () => {
|
||||
<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>
|
||||
<span className="text-gray-400">{t('proposals.votingProgress')}</span>
|
||||
<span className="text-white">{t('proposals.votes', { count: proposal.totalVotes })}</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>
|
||||
<span className="text-green-400 text-xs w-12">{t('proposals.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>
|
||||
<span className="text-red-400 text-xs w-12">{t('proposals.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>
|
||||
@@ -173,22 +175,22 @@ const ProposalsList: React.FC = () => {
|
||||
<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>
|
||||
<span className="text-gray-400">{t('proposals.proposer', { address: proposal.proposer })}</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
{quorumReached ? (
|
||||
<span className="text-green-400">✓ Quorum reached</span>
|
||||
<span className="text-green-400">✓ {t('proposals.quorumReached')}</span>
|
||||
) : (
|
||||
<span className="text-yellow-400">⚠ Quorum: {proposal.quorum}%</span>
|
||||
<span className="text-yellow-400">⚠ {t('proposals.quorum', { percent: proposal.quorum })}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button size="sm" variant="outline" className="border-gray-700">
|
||||
View Details
|
||||
{t('proposals.viewDetails')}
|
||||
</Button>
|
||||
<Button size="sm" className="bg-kurdish-green hover:bg-kurdish-green/80">
|
||||
Cast Vote
|
||||
{t('proposals.castVote')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user