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:
2026-02-22 04:48:20 +03:00
parent 5b26cc8907
commit 4f683538d3
129 changed files with 22442 additions and 4186 deletions
@@ -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>
+28 -29
View File
@@ -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>
+29 -27
View File
@@ -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">&#10003; {t('proposals.quorumReached')}</span>
) : (
<span className="text-yellow-400"> Quorum: {proposal.quorum}%</span>
<span className="text-yellow-400">&#9888; {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>