mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-23 00:07:55 +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 { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -9,6 +10,7 @@ import { COMMISSIONS } from '@/config/commissions';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
|
||||
export function CommissionSetupTab() {
|
||||
const { t } = useTranslation();
|
||||
const { api, isApiReady, selectedAccount } = usePezkuwi();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -50,8 +52,8 @@ export function CommissionSetupTab() {
|
||||
const handleAddMember = async () => {
|
||||
if (!api || !selectedAccount) {
|
||||
toast({
|
||||
title: 'Wallet Not Connected',
|
||||
description: 'Please connect your admin wallet',
|
||||
title: t('commission.setup.walletNotConnected'),
|
||||
description: t('commission.setup.connectWallet'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -59,8 +61,8 @@ export function CommissionSetupTab() {
|
||||
|
||||
if (!newMemberAddress) {
|
||||
toast({
|
||||
title: 'No Addresses',
|
||||
description: 'Please enter at least one address',
|
||||
title: t('commission.setup.noAddresses'),
|
||||
description: t('commission.setup.enterAtLeastOne'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -79,8 +81,8 @@ export function CommissionSetupTab() {
|
||||
|
||||
if (newAddresses.length === 0) {
|
||||
toast({
|
||||
title: 'No Valid Addresses',
|
||||
description: 'Please enter at least one valid address',
|
||||
title: t('commission.setup.noValidAddresses'),
|
||||
description: t('commission.setup.enterValidAddress'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
setProcessing(false);
|
||||
@@ -96,8 +98,8 @@ export function CommissionSetupTab() {
|
||||
|
||||
if (newMembers.length === 0) {
|
||||
toast({
|
||||
title: 'Already Members',
|
||||
description: 'All addresses are already commission members',
|
||||
title: t('commission.setup.alreadyInitialized'),
|
||||
description: t('commission.setup.alreadyInitialized'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
setProcessing(false);
|
||||
@@ -125,21 +127,20 @@ export function CommissionSetupTab() {
|
||||
({ status, dispatchError }) => {
|
||||
if (status.isInBlock || status.isFinalized) {
|
||||
if (dispatchError) {
|
||||
let errorMessage = 'Failed to add member';
|
||||
let errorMessage = t('commission.setup.addMemberFailed');
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
||||
errorMessage = `${decoded.section}.${decoded.name}`;
|
||||
}
|
||||
toast({
|
||||
title: 'Error',
|
||||
title: t('commission.setup.addMemberFailed'),
|
||||
description: errorMessage,
|
||||
variant: 'destructive',
|
||||
});
|
||||
reject(new Error(errorMessage));
|
||||
} else {
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: `${newMembers.length} member(s) added successfully!`,
|
||||
title: t('commission.setup.addMemberSuccess', { count: newMembers.length }),
|
||||
});
|
||||
setNewMemberAddress('');
|
||||
setTimeout(() => checkSetup(), 2000);
|
||||
@@ -152,8 +153,8 @@ export function CommissionSetupTab() {
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) console.error('Error adding member:', error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: error instanceof Error ? error.message : 'Failed to add member',
|
||||
title: t('commission.setup.addMemberFailed'),
|
||||
description: error instanceof Error ? error.message : t('commission.setup.addMemberFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
@@ -164,8 +165,8 @@ export function CommissionSetupTab() {
|
||||
const handleInitializeCommission = async () => {
|
||||
if (!api || !selectedAccount) {
|
||||
toast({
|
||||
title: 'Wallet Not Connected',
|
||||
description: 'Please connect your admin wallet',
|
||||
title: t('commission.setup.walletNotConnected'),
|
||||
description: t('commission.setup.connectWallet'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -209,7 +210,7 @@ export function CommissionSetupTab() {
|
||||
|
||||
if (import.meta.env.DEV) console.error('Setup error:', errorMessage);
|
||||
toast({
|
||||
title: 'Setup Failed',
|
||||
title: t('commission.setup.setupFailed'),
|
||||
description: errorMessage,
|
||||
variant: 'destructive',
|
||||
});
|
||||
@@ -225,8 +226,7 @@ export function CommissionSetupTab() {
|
||||
if (sudidEvent) {
|
||||
if (import.meta.env.DEV) console.log('✅ KYC Commission initialized');
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'KYC Commission initialized successfully!',
|
||||
title: t('commission.setup.kycInitialized'),
|
||||
});
|
||||
resolve();
|
||||
} else {
|
||||
@@ -238,8 +238,8 @@ export function CommissionSetupTab() {
|
||||
).catch((error) => {
|
||||
if (import.meta.env.DEV) console.error('Failed to sign and send:', error);
|
||||
toast({
|
||||
title: 'Transaction Error',
|
||||
description: error instanceof Error ? error.message : 'Failed to submit transaction',
|
||||
title: t('commission.setup.transactionError'),
|
||||
description: error instanceof Error ? error.message : t('commission.setup.failedToSubmit'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
reject(error);
|
||||
@@ -252,8 +252,8 @@ export function CommissionSetupTab() {
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) console.error('Error initializing commission:', error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: error instanceof Error ? error.message : 'Failed to initialize commission',
|
||||
title: t('commission.setup.setupFailed'),
|
||||
description: error instanceof Error ? error.message : t('commission.setup.failedToInitialize'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
@@ -267,7 +267,7 @@ export function CommissionSetupTab() {
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-cyan-500" />
|
||||
<span className="ml-3 text-gray-400">Connecting to blockchain...</span>
|
||||
<span className="ml-3 text-gray-400">{t('commission.setup.connecting')}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -281,7 +281,7 @@ export function CommissionSetupTab() {
|
||||
<Alert>
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
Please connect your admin wallet to manage commission setup.
|
||||
{t('commission.setup.connectWalletAlert')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</CardContent>
|
||||
@@ -296,7 +296,7 @@ export function CommissionSetupTab() {
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Shield className="w-5 h-5" />
|
||||
KYC Commission Setup
|
||||
{t('commission.setup.statusLabel')}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
@@ -308,28 +308,28 @@ export function CommissionSetupTab() {
|
||||
<>
|
||||
<div className="flex items-center justify-between p-4 bg-gray-800/50 rounded-lg border border-gray-700">
|
||||
<div>
|
||||
<p className="font-medium">Commission Status</p>
|
||||
<p className="font-medium">{t('commission.setup.statusLabel')}</p>
|
||||
<p className="text-sm text-gray-400 mt-1">
|
||||
{setupComplete
|
||||
? 'Commission is initialized and ready'
|
||||
: 'Commission needs to be initialized'}
|
||||
? t('commission.setup.initialized')
|
||||
: t('commission.setup.notInitialized')}
|
||||
</p>
|
||||
</div>
|
||||
{setupComplete ? (
|
||||
<Badge className="bg-green-600">
|
||||
<CheckCircle className="w-3 h-3 mr-1" />
|
||||
Ready
|
||||
{t('commission.setup.ready')}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="secondary">
|
||||
<AlertTriangle className="w-3 h-3 mr-1" />
|
||||
Not Initialized
|
||||
{t('commission.setup.notInitializedBadge')}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-gray-400">Proxy Account</p>
|
||||
<p className="text-sm font-medium text-gray-400">{t('commission.setup.proxyAccount')}</p>
|
||||
<div className="p-3 bg-gray-800/50 rounded border border-gray-700">
|
||||
<p className="font-mono text-xs">{COMMISSIONS.KYC.proxyAccount}</p>
|
||||
</div>
|
||||
@@ -337,11 +337,11 @@ export function CommissionSetupTab() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium text-gray-400">
|
||||
Commission Members ({commissionMembers.length})
|
||||
{t('commission.setup.membersLabel')} ({commissionMembers.length})
|
||||
</p>
|
||||
{commissionMembers.length === 0 ? (
|
||||
<div className="p-4 bg-gray-800/50 rounded border border-gray-700 text-center text-gray-500">
|
||||
No members yet
|
||||
{t('commission.setup.noMembers')}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
@@ -352,7 +352,7 @@ export function CommissionSetupTab() {
|
||||
>
|
||||
<p className="font-mono text-xs">{member}</p>
|
||||
{member === COMMISSIONS.KYC.proxyAccount && (
|
||||
<Badge className="mt-2 bg-cyan-600 text-xs">KYC Proxy</Badge>
|
||||
<Badge className="mt-2 bg-cyan-600 text-xs">{t('commission.setup.kycProxy')}</Badge>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
@@ -364,34 +364,33 @@ export function CommissionSetupTab() {
|
||||
<Alert className="bg-yellow-500/10 border-yellow-500/30">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<strong>Required:</strong> Initialize the commission before members can join.
|
||||
This requires sudo privileges.
|
||||
{t('commission.setup.initRequired')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{setupComplete && (
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm font-medium text-gray-400">Add Members</p>
|
||||
<p className="text-sm font-medium text-gray-400">{t('commission.setup.addMembersTitle')}</p>
|
||||
<div className="flex gap-2 mb-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
// Get wallet addresses from Pezkuwi.js extension
|
||||
// For now, show instruction
|
||||
toast({
|
||||
title: 'Get Addresses',
|
||||
description: 'Copy addresses from Pezkuwi.js and paste below',
|
||||
title: t('commission.setup.getAddresses'),
|
||||
description: t('commission.setup.getAddressesToast'),
|
||||
});
|
||||
}}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
>
|
||||
How to get addresses
|
||||
{t('commission.setup.howToGetAddresses')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<textarea
|
||||
placeholder="Member addresses, one per line"
|
||||
placeholder={t('commission.setup.addressPlaceholder')}
|
||||
value={newMemberAddress}
|
||||
onChange={(e) => setNewMemberAddress(e.target.value)}
|
||||
className="flex-1 font-mono text-sm p-3 bg-gray-800 border border-gray-700 rounded min-h-[120px] placeholder:text-gray-500 placeholder:opacity-50"
|
||||
@@ -406,7 +405,7 @@ export function CommissionSetupTab() {
|
||||
) : (
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
{processing ? 'Adding Members...' : 'Add Members'}
|
||||
{processing ? t('commission.setup.addingMembers') : t('commission.setup.addMembersBtn')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -421,17 +420,17 @@ export function CommissionSetupTab() {
|
||||
{processing ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Initializing...
|
||||
{t('commission.setup.initializing')}
|
||||
</>
|
||||
) : setupComplete ? (
|
||||
<>
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
Already Initialized
|
||||
{t('commission.setup.alreadyInitialized')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Initialize Commission
|
||||
{t('commission.setup.initializeBtn')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -441,7 +440,7 @@ export function CommissionSetupTab() {
|
||||
variant="outline"
|
||||
disabled={loading}
|
||||
>
|
||||
Refresh
|
||||
{t('commission.setup.refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
@@ -452,22 +451,13 @@ export function CommissionSetupTab() {
|
||||
{/* Instructions */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Setup Instructions</CardTitle>
|
||||
<CardTitle>{t('commission.setup.instructionsTitle')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ol className="list-decimal list-inside space-y-2 text-sm text-gray-400">
|
||||
<li>
|
||||
<strong className="text-white">Initialize Commission</strong> - Add proxy to
|
||||
DynamicCommissionCollective (requires sudo)
|
||||
</li>
|
||||
<li>
|
||||
<strong className="text-white">Join Commission</strong> - Members add proxy rights
|
||||
via Commission Voting tab
|
||||
</li>
|
||||
<li>
|
||||
<strong className="text-white">Start Voting</strong> - Create proposals and vote on
|
||||
KYC applications
|
||||
</li>
|
||||
<li>{t('commission.setup.step1')}</li>
|
||||
<li>{t('commission.setup.step2')}</li>
|
||||
<li>{t('commission.setup.step3')}</li>
|
||||
</ol>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -26,6 +27,7 @@ interface Proposal {
|
||||
}
|
||||
|
||||
export function CommissionVotingTab() {
|
||||
const { t } = useTranslation();
|
||||
const { api, isApiReady, selectedAccount } = usePezkuwi();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -120,8 +122,7 @@ export function CommissionVotingTab() {
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) console.error('Error loading proposals:', error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Failed to load proposals',
|
||||
title: t('commission.voting.loadFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
@@ -132,8 +133,8 @@ export function CommissionVotingTab() {
|
||||
const handleVote = async (proposal: Proposal, approve: boolean) => {
|
||||
if (!api || !selectedAccount) {
|
||||
toast({
|
||||
title: 'Wallet Not Connected',
|
||||
description: 'Please connect your wallet first',
|
||||
title: t('commission.voting.walletNotConnected'),
|
||||
description: t('commission.voting.connectFirst'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -141,8 +142,7 @@ export function CommissionVotingTab() {
|
||||
|
||||
if (!isCommissionMember) {
|
||||
toast({
|
||||
title: 'Not a Commission Member',
|
||||
description: 'You are not a member of the KYC Approval Commission',
|
||||
title: t('commission.voting.notMemberTitle'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -182,7 +182,7 @@ export function CommissionVotingTab() {
|
||||
|
||||
if (import.meta.env.DEV) console.error('Vote error:', errorMessage);
|
||||
toast({
|
||||
title: 'Vote Failed',
|
||||
title: t('commission.voting.voteFailed'),
|
||||
description: errorMessage,
|
||||
variant: 'destructive',
|
||||
});
|
||||
@@ -203,14 +203,14 @@ export function CommissionVotingTab() {
|
||||
if (executedEvent) {
|
||||
if (import.meta.env.DEV) console.log('✅ Proposal executed (threshold reached)');
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Proposal passed and executed! KYC approved.',
|
||||
title: t('commission.voting.proposalPassed'),
|
||||
description: t('commission.voting.kycApproved'),
|
||||
});
|
||||
} else if (votedEvent) {
|
||||
if (import.meta.env.DEV) console.log('✅ Vote recorded');
|
||||
toast({
|
||||
title: 'Vote Recorded',
|
||||
description: `Your ${approve ? 'AYE' : 'NAY'} vote has been recorded`,
|
||||
title: t('commission.voting.voteRecorded'),
|
||||
description: approve ? t('commission.voting.ayeRecorded') : t('commission.voting.nayRecorded'),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -220,8 +220,8 @@ export function CommissionVotingTab() {
|
||||
).catch((error) => {
|
||||
if (import.meta.env.DEV) console.error('Failed to sign and send:', error);
|
||||
toast({
|
||||
title: 'Transaction Error',
|
||||
description: error instanceof Error ? error.message : 'Failed to submit transaction',
|
||||
title: t('commission.voting.submitFailed'),
|
||||
description: error instanceof Error ? error.message : t('commission.voting.submitFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
reject(error);
|
||||
@@ -236,8 +236,8 @@ export function CommissionVotingTab() {
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) console.error('Error voting:', error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: error instanceof Error ? error.message : 'Failed to vote',
|
||||
title: t('commission.voting.voteFailed'),
|
||||
description: error instanceof Error ? error.message : t('commission.voting.voteFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
@@ -248,8 +248,8 @@ export function CommissionVotingTab() {
|
||||
const handleExecute = async (proposal: Proposal) => {
|
||||
if (!api || !selectedAccount) {
|
||||
toast({
|
||||
title: 'Wallet Not Connected',
|
||||
description: 'Please connect your wallet first',
|
||||
title: t('commission.voting.walletNotConnected'),
|
||||
description: t('commission.voting.connectFirst'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -297,7 +297,7 @@ export function CommissionVotingTab() {
|
||||
|
||||
if (import.meta.env.DEV) console.error('Execute error:', errorMessage);
|
||||
toast({
|
||||
title: 'Execute Failed',
|
||||
title: t('commission.voting.executeFailed'),
|
||||
description: errorMessage,
|
||||
variant: 'destructive',
|
||||
});
|
||||
@@ -324,21 +324,21 @@ export function CommissionVotingTab() {
|
||||
if (result && typeof result === 'object' && 'Err' in result) {
|
||||
if (import.meta.env.DEV) console.error('Execution failed:', result.Err);
|
||||
toast({
|
||||
title: 'Execution Failed',
|
||||
description: `Proposal closed but execution failed: ${JSON.stringify(result.Err)}`,
|
||||
title: t('commission.voting.executeFailed'),
|
||||
description: JSON.stringify(result.Err),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: 'Proposal Executed!',
|
||||
description: 'KYC approved and NFT minted successfully!',
|
||||
title: t('commission.voting.executeSuccess'),
|
||||
description: t('commission.voting.kycApproved'),
|
||||
});
|
||||
}
|
||||
} else if (closedEvent) {
|
||||
if (import.meta.env.DEV) console.log('Proposal closed');
|
||||
toast({
|
||||
title: 'Proposal Closed',
|
||||
description: 'Proposal has been closed',
|
||||
title: t('commission.voting.proposalClosed'),
|
||||
description: t('commission.voting.proposalClosedDesc'),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -348,8 +348,8 @@ export function CommissionVotingTab() {
|
||||
).catch((error) => {
|
||||
if (import.meta.env.DEV) console.error('Failed to sign and send:', error);
|
||||
toast({
|
||||
title: 'Transaction Error',
|
||||
description: error instanceof Error ? error.message : 'Failed to submit transaction',
|
||||
title: t('commission.voting.submitFailed'),
|
||||
description: error instanceof Error ? error.message : t('commission.voting.submitFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
reject(error);
|
||||
@@ -363,8 +363,8 @@ export function CommissionVotingTab() {
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) console.error('Error executing:', error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: error instanceof Error ? error.message : 'Failed to execute proposal',
|
||||
title: t('commission.voting.executeFailed'),
|
||||
description: error instanceof Error ? error.message : t('commission.voting.executeFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
@@ -373,19 +373,19 @@ export function CommissionVotingTab() {
|
||||
};
|
||||
|
||||
const getProposalDescription = (call: Record<string, unknown>): string => {
|
||||
if (!call) return 'Unknown proposal';
|
||||
if (!call) return t('commission.voting.unknownProposal');
|
||||
|
||||
try {
|
||||
const callStr = JSON.stringify(call);
|
||||
if (callStr.includes('approveKyc')) {
|
||||
return 'KYC Approval';
|
||||
return t('commission.voting.kycApproval');
|
||||
}
|
||||
if (callStr.includes('rejectKyc')) {
|
||||
return 'KYC Rejection';
|
||||
return t('commission.voting.kycRejection');
|
||||
}
|
||||
return 'Commission Action';
|
||||
return t('commission.voting.commissionAction');
|
||||
} catch {
|
||||
return 'Unknown proposal';
|
||||
return t('commission.voting.unknownProposal');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -393,12 +393,12 @@ export function CommissionVotingTab() {
|
||||
const progress = (proposal.ayes.length / proposal.threshold) * 100;
|
||||
|
||||
if (proposal.ayes.length >= proposal.threshold) {
|
||||
return <Badge variant="default" className="bg-green-600">PASSED</Badge>;
|
||||
return <Badge variant="default" className="bg-green-600">{t('commission.voting.statusPassed')}</Badge>;
|
||||
}
|
||||
if (progress >= 50) {
|
||||
return <Badge variant="default" className="bg-yellow-600">VOTING ({progress.toFixed(0)}%)</Badge>;
|
||||
return <Badge variant="default" className="bg-yellow-600">{t('commission.voting.statusVoting', { progress: progress.toFixed(0) })}</Badge>;
|
||||
}
|
||||
return <Badge variant="secondary">VOTING ({progress.toFixed(0)}%)</Badge>;
|
||||
return <Badge variant="secondary">{t('commission.voting.statusVoting', { progress: progress.toFixed(0) })}</Badge>;
|
||||
};
|
||||
|
||||
if (!isApiReady) {
|
||||
@@ -407,7 +407,7 @@ export function CommissionVotingTab() {
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-center">
|
||||
<Loader2 className="h-6 w-6 animate-spin mr-2" />
|
||||
<span>Connecting to blockchain...</span>
|
||||
<span>{t('commission.voting.connecting')}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -419,7 +419,7 @@ export function CommissionVotingTab() {
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-center text-muted-foreground">
|
||||
<p>Please connect your wallet to view commission proposals</p>
|
||||
<p>{t('commission.voting.noWallet')}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -461,20 +461,19 @@ export function CommissionVotingTab() {
|
||||
({ status, dispatchError }) => {
|
||||
if (status.isInBlock || status.isFinalized) {
|
||||
if (dispatchError) {
|
||||
let errorMessage = 'Failed to join commission';
|
||||
let errorMessage = t('commission.voting.joinFailed');
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
||||
errorMessage = `${decoded.section}.${decoded.name}`;
|
||||
}
|
||||
toast({
|
||||
title: 'Error',
|
||||
title: t('commission.voting.joinFailed'),
|
||||
description: errorMessage,
|
||||
variant: 'destructive',
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'You have joined the KYC Commission!',
|
||||
title: t('commission.voting.joinSuccess'),
|
||||
});
|
||||
setTimeout(() => checkMembership(), 2000);
|
||||
}
|
||||
@@ -483,8 +482,8 @@ export function CommissionVotingTab() {
|
||||
);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: error instanceof Error ? error.message : 'Failed to join commission',
|
||||
title: t('commission.voting.joinFailed'),
|
||||
description: error instanceof Error ? error.message : t('commission.voting.joinFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@@ -494,13 +493,13 @@ export function CommissionVotingTab() {
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-center">
|
||||
<p className="text-muted-foreground mb-4">You are not a member of the KYC Approval Commission</p>
|
||||
<p className="text-sm text-muted-foreground mb-6">Only commission members can view and vote on proposals</p>
|
||||
<p className="text-muted-foreground mb-4">{t('commission.voting.notMember')}</p>
|
||||
<p className="text-sm text-muted-foreground mb-6">{t('commission.voting.onlyMembers')}</p>
|
||||
<Button
|
||||
onClick={handleJoinCommission}
|
||||
className="bg-green-600 hover:bg-green-700"
|
||||
>
|
||||
Join Commission
|
||||
{t('commission.voting.joinBtn')}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -512,9 +511,9 @@ export function CommissionVotingTab() {
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold">Commission Proposals</h2>
|
||||
<h2 className="text-2xl font-bold">{t('commission.voting.title')}</h2>
|
||||
<p className="text-muted-foreground">
|
||||
Active voting proposals for {COMMISSIONS.KYC.name}
|
||||
{t('commission.voting.subtitle', { name: COMMISSIONS.KYC.name })}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
@@ -523,7 +522,7 @@ export function CommissionVotingTab() {
|
||||
variant="outline"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 mr-2 ${loading ? 'animate-spin' : ''}`} />
|
||||
Refresh
|
||||
{t('commission.voting.refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -532,7 +531,7 @@ export function CommissionVotingTab() {
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-center">
|
||||
<Loader2 className="h-6 w-6 animate-spin mr-2" />
|
||||
<span>Loading proposals...</span>
|
||||
<span>{t('commission.voting.loading')}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -541,25 +540,25 @@ export function CommissionVotingTab() {
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-center text-muted-foreground">
|
||||
<Clock className="h-12 w-12 mx-auto mb-4 opacity-50" />
|
||||
<p>No active proposals</p>
|
||||
<p className="text-sm mt-2">Proposals will appear here when commission members create them</p>
|
||||
<p>{t('commission.voting.noProposals')}</p>
|
||||
<p className="text-sm mt-2">{t('commission.voting.noProposalsHelp')}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Active Proposals ({proposals.length})</CardTitle>
|
||||
<CardTitle>{t('commission.voting.activeProposals', { count: proposals.length })}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Proposal</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Votes</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
<TableHead>{t('commission.voting.tableProposal')}</TableHead>
|
||||
<TableHead>{t('commission.voting.tableType')}</TableHead>
|
||||
<TableHead>{t('commission.voting.tableStatus')}</TableHead>
|
||||
<TableHead>{t('commission.voting.tableVotes')}</TableHead>
|
||||
<TableHead className="text-right">{t('commission.voting.tableActions')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -602,7 +601,7 @@ export function CommissionVotingTab() {
|
||||
{voting === proposal.hash ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<>Execute Proposal</>
|
||||
<>{t('commission.voting.execute')}</>
|
||||
)}
|
||||
</Button>
|
||||
) : (
|
||||
@@ -619,7 +618,7 @@ export function CommissionVotingTab() {
|
||||
) : (
|
||||
<>
|
||||
<ThumbsUp className="h-4 w-4 mr-1" />
|
||||
Aye
|
||||
{t('commission.voting.aye')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
@@ -634,7 +633,7 @@ export function CommissionVotingTab() {
|
||||
) : (
|
||||
<>
|
||||
<ThumbsDown className="h-4 w-4 mr-1" />
|
||||
Nay
|
||||
{t('commission.voting.nay')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { supabase } from '@/lib/supabase';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -82,12 +83,12 @@ interface Evidence {
|
||||
review_notes?: string;
|
||||
}
|
||||
|
||||
// Decision options
|
||||
const DECISION_OPTIONS = [
|
||||
{ value: 'release_to_buyer', label: 'Release to Buyer', description: 'Release escrowed crypto to the buyer' },
|
||||
{ value: 'refund_to_seller', label: 'Refund to Seller', description: 'Return escrowed crypto to the seller' },
|
||||
{ value: 'split', label: 'Split 50/50', description: 'Split the escrowed amount between both parties' },
|
||||
{ value: 'escalate', label: 'Escalate', description: 'Escalate to higher authority for complex cases' }
|
||||
// Decision option values - labels are translated via t() in the component
|
||||
const DECISION_OPTION_KEYS = [
|
||||
{ value: 'release_to_buyer', labelKey: 'dispute.releaseToBuyer' },
|
||||
{ value: 'refund_to_seller', labelKey: 'dispute.refundToSeller' },
|
||||
{ value: 'split', labelKey: 'dispute.split' },
|
||||
{ value: 'escalate', labelKey: 'dispute.escalate' },
|
||||
];
|
||||
|
||||
// Status badge colors
|
||||
@@ -99,18 +100,19 @@ const STATUS_COLORS: Record<string, string> = {
|
||||
closed: 'bg-gray-500/20 text-gray-400 border-gray-500/30'
|
||||
};
|
||||
|
||||
// Category labels
|
||||
const CATEGORY_LABELS: Record<string, string> = {
|
||||
payment_not_received: 'Payment Not Received',
|
||||
wrong_amount: 'Wrong Amount',
|
||||
fake_payment_proof: 'Fake Payment Proof',
|
||||
seller_not_responding: 'Seller Not Responding',
|
||||
buyer_not_responding: 'Buyer Not Responding',
|
||||
fraudulent_behavior: 'Fraudulent Behavior',
|
||||
other: 'Other'
|
||||
// Category translation keys
|
||||
const CATEGORY_KEYS: Record<string, string> = {
|
||||
payment_not_received: 'dispute.categoryPaymentNotReceived',
|
||||
wrong_amount: 'dispute.categoryWrongAmount',
|
||||
fake_payment_proof: 'dispute.categoryFakePaymentProof',
|
||||
seller_not_responding: 'dispute.categorySellerNotResponding',
|
||||
buyer_not_responding: 'dispute.categoryBuyerNotResponding',
|
||||
fraudulent_behavior: 'dispute.categoryFraudulentBehavior',
|
||||
other: 'dispute.categoryOther'
|
||||
};
|
||||
|
||||
export function DisputeResolutionPanel() {
|
||||
const { t } = useTranslation();
|
||||
const [disputes, setDisputes] = useState<Dispute[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedDispute, setSelectedDispute] = useState<Dispute | null>(null);
|
||||
@@ -164,7 +166,7 @@ export function DisputeResolutionPanel() {
|
||||
setDisputes(disputesWithEvidence);
|
||||
} catch (error) {
|
||||
console.error('Error fetching disputes:', error);
|
||||
toast.error('Failed to load disputes');
|
||||
toast.error(t('dispute.loadFailed'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -216,18 +218,18 @@ export function DisputeResolutionPanel() {
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
toast.success('Dispute claimed for review');
|
||||
toast.success(t('dispute.claimedToast'));
|
||||
fetchDisputes();
|
||||
} catch (error) {
|
||||
console.error('Error claiming dispute:', error);
|
||||
toast.error('Failed to claim dispute');
|
||||
toast.error(t('dispute.claimFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
// Resolve dispute
|
||||
const resolveDispute = async () => {
|
||||
if (!selectedDispute || !decision || !reasoning) {
|
||||
toast.error('Please select a decision and provide reasoning');
|
||||
toast.error(t('dispute.noDecision'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -265,7 +267,7 @@ export function DisputeResolutionPanel() {
|
||||
p_user_id: selectedDispute.trade.seller_id,
|
||||
p_type: 'dispute_resolved',
|
||||
p_title: 'Dispute Resolved',
|
||||
p_message: `The dispute has been resolved: ${DECISION_OPTIONS.find(o => o.value === decision)?.label}`,
|
||||
p_message: `The dispute has been resolved: ${t(DECISION_OPTION_KEYS.find(o => o.value === decision)?.labelKey || '')}`,
|
||||
p_reference_type: 'dispute',
|
||||
p_reference_id: selectedDispute.id
|
||||
}),
|
||||
@@ -273,7 +275,7 @@ export function DisputeResolutionPanel() {
|
||||
p_user_id: selectedDispute.trade.buyer_id,
|
||||
p_type: 'dispute_resolved',
|
||||
p_title: 'Dispute Resolved',
|
||||
p_message: `The dispute has been resolved: ${DECISION_OPTIONS.find(o => o.value === decision)?.label}`,
|
||||
p_message: `The dispute has been resolved: ${t(DECISION_OPTION_KEYS.find(o => o.value === decision)?.labelKey || '')}`,
|
||||
p_reference_type: 'dispute',
|
||||
p_reference_id: selectedDispute.id
|
||||
})
|
||||
@@ -281,7 +283,7 @@ export function DisputeResolutionPanel() {
|
||||
await Promise.all(notificationPromises);
|
||||
}
|
||||
|
||||
toast.success('Dispute resolved successfully');
|
||||
toast.success(t('dispute.resolvedToast'));
|
||||
setResolveOpen(false);
|
||||
setSelectedDispute(null);
|
||||
setDecision('');
|
||||
@@ -289,7 +291,7 @@ export function DisputeResolutionPanel() {
|
||||
fetchDisputes();
|
||||
} catch (error) {
|
||||
console.error('Error resolving dispute:', error);
|
||||
toast.error('Failed to resolve dispute');
|
||||
toast.error(t('dispute.resolveFailed'));
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
@@ -322,15 +324,15 @@ export function DisputeResolutionPanel() {
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold flex items-center gap-2">
|
||||
<Gavel className="h-6 w-6 text-kurdish-green" />
|
||||
Dispute Resolution
|
||||
{t('dispute.title')}
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-sm mt-1">
|
||||
Review and resolve P2P trading disputes
|
||||
{t('dispute.subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" onClick={fetchDisputes} disabled={loading}>
|
||||
<RefreshCw className={`h-4 w-4 mr-2 ${loading ? 'animate-spin' : ''}`} />
|
||||
Refresh
|
||||
{t('dispute.refresh')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -340,7 +342,7 @@ export function DisputeResolutionPanel() {
|
||||
<CardContent className="pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Open</p>
|
||||
<p className="text-sm text-muted-foreground">{t('dispute.statsOpen')}</p>
|
||||
<p className="text-2xl font-bold text-yellow-500">{stats.open}</p>
|
||||
</div>
|
||||
<AlertTriangle className="h-8 w-8 text-yellow-500/50" />
|
||||
@@ -352,7 +354,7 @@ export function DisputeResolutionPanel() {
|
||||
<CardContent className="pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Under Review</p>
|
||||
<p className="text-sm text-muted-foreground">{t('dispute.statsUnderReview')}</p>
|
||||
<p className="text-2xl font-bold text-blue-500">{stats.under_review}</p>
|
||||
</div>
|
||||
<Clock className="h-8 w-8 text-blue-500/50" />
|
||||
@@ -364,7 +366,7 @@ export function DisputeResolutionPanel() {
|
||||
<CardContent className="pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Resolved</p>
|
||||
<p className="text-sm text-muted-foreground">{t('dispute.statsResolved')}</p>
|
||||
<p className="text-2xl font-bold text-green-500">{stats.resolved}</p>
|
||||
</div>
|
||||
<CheckCircle2 className="h-8 w-8 text-green-500/50" />
|
||||
@@ -376,7 +378,7 @@ export function DisputeResolutionPanel() {
|
||||
<CardContent className="pt-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm text-muted-foreground">Escalated</p>
|
||||
<p className="text-sm text-muted-foreground">{t('dispute.statsEscalated')}</p>
|
||||
<p className="text-2xl font-bold text-purple-500">{stats.escalated}</p>
|
||||
</div>
|
||||
<Scale className="h-8 w-8 text-purple-500/50" />
|
||||
@@ -389,16 +391,16 @@ export function DisputeResolutionPanel() {
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="grid grid-cols-4 w-full max-w-md">
|
||||
<TabsTrigger value="open" className="gap-1">
|
||||
Open
|
||||
{t('dispute.statsOpen')}
|
||||
{stats.open > 0 && (
|
||||
<Badge variant="secondary" className="ml-1 h-5 px-1.5">
|
||||
{stats.open}
|
||||
</Badge>
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="under_review">In Review</TabsTrigger>
|
||||
<TabsTrigger value="resolved">Resolved</TabsTrigger>
|
||||
<TabsTrigger value="escalated">Escalated</TabsTrigger>
|
||||
<TabsTrigger value="under_review">{t('dispute.tabInReview')}</TabsTrigger>
|
||||
<TabsTrigger value="resolved">{t('dispute.statsResolved')}</TabsTrigger>
|
||||
<TabsTrigger value="escalated">{t('dispute.statsEscalated')}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value={activeTab} className="mt-4">
|
||||
@@ -412,7 +414,7 @@ export function DisputeResolutionPanel() {
|
||||
<Card>
|
||||
<CardContent className="py-12 text-center">
|
||||
<Shield className="h-12 w-12 mx-auto text-muted-foreground/50 mb-4" />
|
||||
<p className="text-muted-foreground">No disputes in this category</p>
|
||||
<p className="text-muted-foreground">{t('dispute.empty')}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
@@ -427,12 +429,12 @@ export function DisputeResolutionPanel() {
|
||||
{dispute.status.replace('_', ' ').toUpperCase()}
|
||||
</Badge>
|
||||
<Badge variant="outline">
|
||||
{CATEGORY_LABELS[dispute.category] || dispute.category}
|
||||
{t(CATEGORY_KEYS[dispute.category] || dispute.category)}
|
||||
</Badge>
|
||||
{dispute.evidence && dispute.evidence.length > 0 && (
|
||||
<Badge variant="secondary" className="gap-1">
|
||||
<ImageIcon className="h-3 w-3" />
|
||||
{dispute.evidence.length} evidence
|
||||
{t('dispute.evidence', { count: dispute.evidence.length })}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
@@ -466,7 +468,7 @@ export function DisputeResolutionPanel() {
|
||||
onClick={() => openDetails(dispute)}
|
||||
>
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
View
|
||||
{t('dispute.view')}
|
||||
</Button>
|
||||
|
||||
{dispute.status === 'open' && (
|
||||
@@ -474,7 +476,7 @@ export function DisputeResolutionPanel() {
|
||||
size="sm"
|
||||
onClick={() => claimDispute(dispute.id)}
|
||||
>
|
||||
Claim
|
||||
{t('dispute.claim')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -485,7 +487,7 @@ export function DisputeResolutionPanel() {
|
||||
onClick={() => openResolve(dispute)}
|
||||
>
|
||||
<Gavel className="h-4 w-4 mr-1" />
|
||||
Resolve
|
||||
{t('dispute.resolve')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
@@ -504,10 +506,10 @@ export function DisputeResolutionPanel() {
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Scale className="h-5 w-5" />
|
||||
Dispute Details
|
||||
{t('dispute.detailsTitle')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Review all information related to this dispute
|
||||
{t('dispute.detailsDesc')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -520,13 +522,13 @@ export function DisputeResolutionPanel() {
|
||||
{selectedDispute.status.replace('_', ' ').toUpperCase()}
|
||||
</Badge>
|
||||
<Badge variant="outline">
|
||||
{CATEGORY_LABELS[selectedDispute.category] || selectedDispute.category}
|
||||
{t(CATEGORY_KEYS[selectedDispute.category] || selectedDispute.category)}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Reason */}
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">Reason</h4>
|
||||
<h4 className="font-medium mb-2">{t('dispute.reason')}</h4>
|
||||
<p className="text-sm text-muted-foreground bg-muted p-3 rounded-lg">
|
||||
{selectedDispute.reason}
|
||||
</p>
|
||||
@@ -535,22 +537,22 @@ export function DisputeResolutionPanel() {
|
||||
{/* Trade Info */}
|
||||
{selectedDispute.trade && (
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">Trade Information</h4>
|
||||
<h4 className="font-medium mb-2">{t('dispute.tradeInfo')}</h4>
|
||||
<div className="bg-muted p-3 rounded-lg space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Trade ID:</span>
|
||||
<span className="text-muted-foreground">{t('dispute.tradeId')}:</span>
|
||||
<span className="font-mono">{formatAddress(selectedDispute.trade_id)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Amount:</span>
|
||||
<span className="text-muted-foreground">{t('dispute.amount')}:</span>
|
||||
<span>{selectedDispute.trade.crypto_amount} crypto</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Fiat:</span>
|
||||
<span className="text-muted-foreground">{t('dispute.fiat')}:</span>
|
||||
<span>{selectedDispute.trade.fiat_amount}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Trade Status:</span>
|
||||
<span className="text-muted-foreground">{t('dispute.tradeStatus')}:</span>
|
||||
<Badge variant="secondary">{selectedDispute.trade.status}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
@@ -560,12 +562,12 @@ export function DisputeResolutionPanel() {
|
||||
{/* Parties */}
|
||||
{selectedDispute.trade && (
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">Parties</h4>
|
||||
<h4 className="font-medium mb-2">{t('dispute.parties')}</h4>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div className="bg-muted p-3 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<User className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm font-medium">Seller</span>
|
||||
<span className="text-sm font-medium">{t('dispute.seller')}</span>
|
||||
</div>
|
||||
<p className="text-xs font-mono text-muted-foreground">
|
||||
{formatAddress(selectedDispute.trade.seller_id)}
|
||||
@@ -574,7 +576,7 @@ export function DisputeResolutionPanel() {
|
||||
<div className="bg-muted p-3 rounded-lg">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<User className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="text-sm font-medium">Buyer</span>
|
||||
<span className="text-sm font-medium">{t('dispute.buyer')}</span>
|
||||
</div>
|
||||
<p className="text-xs font-mono text-muted-foreground">
|
||||
{formatAddress(selectedDispute.trade.buyer_id)}
|
||||
@@ -587,7 +589,7 @@ export function DisputeResolutionPanel() {
|
||||
{/* Evidence */}
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">
|
||||
Evidence ({selectedDispute.evidence?.length || 0})
|
||||
{t('dispute.evidence', { count: selectedDispute.evidence?.length || 0 })}
|
||||
</h4>
|
||||
{selectedDispute.evidence && selectedDispute.evidence.length > 0 ? (
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
@@ -624,30 +626,30 @@ export function DisputeResolutionPanel() {
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">No evidence uploaded</p>
|
||||
<p className="text-sm text-muted-foreground">{t('dispute.noEvidence')}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Timeline */}
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">Timeline</h4>
|
||||
<h4 className="font-medium mb-2">{t('dispute.timeline')}</h4>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-yellow-500" />
|
||||
<span className="text-muted-foreground">Opened:</span>
|
||||
<span className="text-muted-foreground">{t('dispute.opened')}:</span>
|
||||
<span>{formatDate(selectedDispute.created_at)}</span>
|
||||
</div>
|
||||
{selectedDispute.assigned_at && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-blue-500" />
|
||||
<span className="text-muted-foreground">Claimed:</span>
|
||||
<span className="text-muted-foreground">{t('dispute.claimed')}:</span>
|
||||
<span>{formatDate(selectedDispute.assigned_at)}</span>
|
||||
</div>
|
||||
)}
|
||||
{selectedDispute.resolved_at && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-green-500" />
|
||||
<span className="text-muted-foreground">Resolved:</span>
|
||||
<span className="text-muted-foreground">{t('dispute.resolved')}:</span>
|
||||
<span>{formatDate(selectedDispute.resolved_at)}</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -657,10 +659,10 @@ export function DisputeResolutionPanel() {
|
||||
{/* Resolution (if resolved) */}
|
||||
{selectedDispute.decision && (
|
||||
<div>
|
||||
<h4 className="font-medium mb-2">Resolution</h4>
|
||||
<h4 className="font-medium mb-2">{t('dispute.resolution')}</h4>
|
||||
<div className="bg-green-500/10 border border-green-500/20 p-3 rounded-lg">
|
||||
<Badge className="bg-green-500/20 text-green-500 mb-2">
|
||||
{DECISION_OPTIONS.find(o => o.value === selectedDispute.decision)?.label}
|
||||
{t(DECISION_OPTION_KEYS.find(o => o.value === selectedDispute.decision)?.labelKey || '')}
|
||||
</Badge>
|
||||
{selectedDispute.decision_reasoning && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
@@ -676,7 +678,7 @@ export function DisputeResolutionPanel() {
|
||||
|
||||
<DialogFooter className="mt-4">
|
||||
<Button variant="outline" onClick={() => setDetailsOpen(false)}>
|
||||
Close
|
||||
{t('dispute.close')}
|
||||
</Button>
|
||||
{selectedDispute?.status === 'under_review' && (
|
||||
<Button
|
||||
@@ -687,7 +689,7 @@ export function DisputeResolutionPanel() {
|
||||
}}
|
||||
>
|
||||
<Gavel className="h-4 w-4 mr-2" />
|
||||
Resolve Dispute
|
||||
{t('dispute.resolve')}
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
@@ -700,29 +702,26 @@ export function DisputeResolutionPanel() {
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Gavel className="h-5 w-5 text-kurdish-green" />
|
||||
Resolve Dispute
|
||||
{t('dispute.resolveTitle')}
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make a final decision on this dispute. This action cannot be undone.
|
||||
{t('dispute.resolveDesc')}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* Decision */}
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-2 block">Decision</label>
|
||||
<label className="text-sm font-medium mb-2 block">{t('dispute.decision')}</label>
|
||||
<Select value={decision} onValueChange={setDecision}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a decision..." />
|
||||
<SelectValue placeholder={t('dispute.decisionPlaceholder')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{DECISION_OPTIONS.map((option) => (
|
||||
{DECISION_OPTION_KEYS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<div className="flex flex-col">
|
||||
<span>{option.label}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{option.description}
|
||||
</span>
|
||||
<span>{t(option.labelKey)}</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -733,16 +732,16 @@ export function DisputeResolutionPanel() {
|
||||
{/* Reasoning */}
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-2 block">
|
||||
Reasoning <span className="text-muted-foreground">(required)</span>
|
||||
{t('dispute.reasoning')} <span className="text-muted-foreground">({t('dispute.required')})</span>
|
||||
</label>
|
||||
<Textarea
|
||||
value={reasoning}
|
||||
onChange={(e) => setReasoning(e.target.value)}
|
||||
placeholder="Explain your decision based on the evidence..."
|
||||
placeholder={t('dispute.reasoningPlaceholder')}
|
||||
rows={4}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
This will be visible to both parties
|
||||
{t('dispute.reasoningHint')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -750,10 +749,9 @@ export function DisputeResolutionPanel() {
|
||||
<div className="flex items-start gap-2 p-3 bg-yellow-500/10 border border-yellow-500/20 rounded-lg">
|
||||
<AlertTriangle className="h-5 w-5 text-yellow-500 flex-shrink-0 mt-0.5" />
|
||||
<div className="text-sm">
|
||||
<p className="font-medium text-yellow-500">Important</p>
|
||||
<p className="font-medium text-yellow-500">{t('dispute.warningTitle')}</p>
|
||||
<p className="text-muted-foreground">
|
||||
Your decision will trigger automatic actions on the escrowed funds.
|
||||
Make sure you have reviewed all evidence carefully.
|
||||
{t('dispute.warningText')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -765,7 +763,7 @@ export function DisputeResolutionPanel() {
|
||||
onClick={() => setResolveOpen(false)}
|
||||
disabled={submitting}
|
||||
>
|
||||
Cancel
|
||||
{t('dispute.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
className="bg-kurdish-green hover:bg-kurdish-green-dark"
|
||||
@@ -777,7 +775,7 @@ export function DisputeResolutionPanel() {
|
||||
) : (
|
||||
<CheckCircle2 className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
Confirm Resolution
|
||||
{t('dispute.confirmResolution')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -18,6 +19,7 @@ import { approveReferral, getPendingApprovalsForReferrer } from '@pezkuwi/lib/ci
|
||||
import type { PendingApproval } from '@pezkuwi/lib/citizenship-workflow';
|
||||
|
||||
export function KycApprovalTab() {
|
||||
const { t } = useTranslation();
|
||||
// identityKyc pallet is on People Chain - use peopleApi
|
||||
const { peopleApi, isPeopleReady, selectedAccount, connectWallet } = usePezkuwi();
|
||||
const { toast } = useToast();
|
||||
@@ -52,8 +54,8 @@ export function KycApprovalTab() {
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) console.error('Error loading pending applications:', error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Failed to load pending applications',
|
||||
title: t('kyc.approval.failed'),
|
||||
description: t('kyc.approval.failedDesc'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
@@ -64,8 +66,8 @@ export function KycApprovalTab() {
|
||||
const handleApproveReferral = async (applicantAddress: string) => {
|
||||
if (!peopleApi || !selectedAccount) {
|
||||
toast({
|
||||
title: 'Wallet Not Connected',
|
||||
description: 'Please connect your wallet first',
|
||||
title: t('kyc.approval.walletNotConnected'),
|
||||
description: t('kyc.approval.connectFirst'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -77,16 +79,16 @@ export function KycApprovalTab() {
|
||||
|
||||
if (!result.success) {
|
||||
toast({
|
||||
title: 'Approval Failed',
|
||||
description: result.error || 'Failed to approve referral',
|
||||
title: t('kyc.approval.failed'),
|
||||
description: result.error || t('kyc.approval.failedDesc'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: 'Referral Approved',
|
||||
description: `Successfully vouched for ${applicantAddress.slice(0, 8)}...${applicantAddress.slice(-4)}`,
|
||||
title: t('kyc.approval.success'),
|
||||
description: t('kyc.approval.successDesc', { address: `${applicantAddress.slice(0, 8)}...${applicantAddress.slice(-4)}` }),
|
||||
});
|
||||
|
||||
// Reload after approval
|
||||
@@ -94,8 +96,8 @@ export function KycApprovalTab() {
|
||||
} catch (error) {
|
||||
if (import.meta.env.DEV) console.error('Error approving referral:', error);
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: error instanceof Error ? error.message : 'Failed to approve referral',
|
||||
title: t('kyc.approval.failed'),
|
||||
description: error instanceof Error ? error.message : t('kyc.approval.failedDesc'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
@@ -109,7 +111,7 @@ export function KycApprovalTab() {
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-cyan-500" />
|
||||
<span className="ml-3 text-gray-400">Connecting to People Chain...</span>
|
||||
<span className="ml-3 text-gray-400">{t('kyc.approval.connecting')}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -123,9 +125,9 @@ export function KycApprovalTab() {
|
||||
<Alert>
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
Please connect your wallet to view referral approvals.
|
||||
{t('kyc.approval.noWallet')}
|
||||
<Button onClick={connectWallet} variant="outline" className="ml-4">
|
||||
Connect Wallet
|
||||
{t('kyc.approval.connectWallet')}
|
||||
</Button>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
@@ -137,9 +139,9 @@ export function KycApprovalTab() {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<CardTitle>Pending Referral Approvals</CardTitle>
|
||||
<CardTitle>{t('kyc.approval.title')}</CardTitle>
|
||||
<Button onClick={loadPendingApplications} variant="outline" size="sm" disabled={loading}>
|
||||
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : 'Refresh'}
|
||||
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : t('kyc.approval.refresh')}
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@@ -150,21 +152,21 @@ export function KycApprovalTab() {
|
||||
) : pendingApps.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<CheckCircle className="w-12 h-12 text-green-500 mx-auto mb-3" />
|
||||
<p className="text-gray-400">No pending approvals</p>
|
||||
<p className="text-sm text-gray-600 mt-2">No one is waiting for your referral approval</p>
|
||||
<p className="text-gray-400">{t('kyc.approval.noApprovals')}</p>
|
||||
<p className="text-sm text-gray-600 mt-2">{t('kyc.approval.noApprovalsHelp')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
These users listed you as their referrer. Approve to vouch for their identity.
|
||||
{t('kyc.approval.helpText')}
|
||||
</p>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Applicant</TableHead>
|
||||
<TableHead>Identity Hash</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Actions</TableHead>
|
||||
<TableHead>{t('kyc.approval.tableApplicant')}</TableHead>
|
||||
<TableHead>{t('kyc.approval.tableIdentityHash')}</TableHead>
|
||||
<TableHead>{t('kyc.approval.tableStatus')}</TableHead>
|
||||
<TableHead>{t('kyc.approval.tableActions')}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
@@ -186,7 +188,7 @@ export function KycApprovalTab() {
|
||||
<TableCell>
|
||||
<Badge className="bg-yellow-500/20 text-yellow-400 border-yellow-500/30">
|
||||
<Clock className="w-3 h-3 mr-1" />
|
||||
Pending Referral
|
||||
{t('kyc.approval.statusPending')}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
@@ -201,7 +203,7 @@ export function KycApprovalTab() {
|
||||
) : (
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
Approve
|
||||
{t('kyc.approval.approve')}
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { usePezkuwi } from '@/contexts/PezkuwiContext';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import {
|
||||
@@ -65,6 +66,7 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
onClose,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
// Use Asset Hub API for asset registration (Step 5) and XCM testing (Step 6)
|
||||
// Steps 1-4 connect to relay chain directly via xcm-wizard functions
|
||||
const { assetHubApi, isAssetHubReady } = usePezkuwi();
|
||||
@@ -138,8 +140,8 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
const handleReserveParaId = async () => {
|
||||
if (!account || !signer) {
|
||||
toast({
|
||||
title: 'Wallet not connected',
|
||||
description: 'Please connect your wallet first',
|
||||
title: t('xcmWizard.walletNotConnected'),
|
||||
description: t('xcmWizard.connectFirst'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -163,8 +165,7 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
}));
|
||||
|
||||
toast({
|
||||
title: 'ParaId Reserved!',
|
||||
description: `Successfully reserved ParaId ${paraId} on ${relayChain}`,
|
||||
title: t('xcmWizard.reserveSuccess', { paraId, chain: relayChain }),
|
||||
});
|
||||
|
||||
// Auto-advance to next step
|
||||
@@ -178,8 +179,8 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
1: { completed: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
}));
|
||||
toast({
|
||||
title: 'Reservation Failed',
|
||||
description: error instanceof Error ? error.message : 'Failed to reserve ParaId',
|
||||
title: t('xcmWizard.reserveFailed'),
|
||||
description: error instanceof Error ? error.message : t('xcmWizard.reserveFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
@@ -205,8 +206,7 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
}));
|
||||
|
||||
toast({
|
||||
title: 'Artifacts Generated!',
|
||||
description: 'Genesis state and runtime WASM are ready for download',
|
||||
title: t('xcmWizard.artifactsReady'),
|
||||
});
|
||||
|
||||
setCurrentStep(3);
|
||||
@@ -217,8 +217,7 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
2: { completed: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
}));
|
||||
toast({
|
||||
title: 'Generation Failed',
|
||||
description: 'Failed to generate chain artifacts',
|
||||
title: t('xcmWizard.artifactsFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
@@ -232,8 +231,7 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
const handleRegisterParachain = async () => {
|
||||
if (!reservedParaId || !genesisFile || !wasmFile || !account || !signer) {
|
||||
toast({
|
||||
title: 'Missing Data',
|
||||
description: 'Please upload both genesis and WASM files',
|
||||
title: t('xcmWizard.missingFiles'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -254,8 +252,7 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
}));
|
||||
|
||||
toast({
|
||||
title: 'Parachain Registered!',
|
||||
description: `ParaId ${reservedParaId} registered on ${relayChain}`,
|
||||
title: t('xcmWizard.registerSuccess'),
|
||||
});
|
||||
|
||||
setCurrentStep(4);
|
||||
@@ -268,8 +265,8 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
3: { completed: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
}));
|
||||
toast({
|
||||
title: 'Registration Failed',
|
||||
description: error instanceof Error ? error.message : 'Failed to register parachain',
|
||||
title: t('xcmWizard.registerFailed'),
|
||||
description: error instanceof Error ? error.message : t('xcmWizard.registerFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
@@ -302,8 +299,7 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
}));
|
||||
|
||||
toast({
|
||||
title: 'HRMP Channels Opened!',
|
||||
description: `Opened ${channels.length} channel(s) with Asset Hub`,
|
||||
title: t('xcmWizard.hrmpSuccess', { count: channels.length }),
|
||||
});
|
||||
|
||||
setCurrentStep(5);
|
||||
@@ -316,8 +312,8 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
4: { completed: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
}));
|
||||
toast({
|
||||
title: 'Channel Opening Failed',
|
||||
description: error instanceof Error ? error.message : 'Failed to open HRMP channels',
|
||||
title: t('xcmWizard.hrmpFailed'),
|
||||
description: error instanceof Error ? error.message : t('xcmWizard.hrmpFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
@@ -331,8 +327,7 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
const handleRegisterAssets = async () => {
|
||||
if (!assetHubApi || !isAssetHubReady || !account || !signer) {
|
||||
toast({
|
||||
title: 'Not Ready',
|
||||
description: 'Please wait for Asset Hub connection',
|
||||
title: t('xcmWizard.notReady'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -381,8 +376,7 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
}));
|
||||
|
||||
toast({
|
||||
title: 'Assets Registered!',
|
||||
description: `Registered ${registered.length} foreign asset(s)`,
|
||||
title: t('xcmWizard.assetsSuccess', { count: registered.length }),
|
||||
});
|
||||
|
||||
setCurrentStep(6);
|
||||
@@ -393,8 +387,8 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
5: { completed: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
}));
|
||||
toast({
|
||||
title: 'Asset Registration Failed',
|
||||
description: error instanceof Error ? error.message : 'Failed to register foreign assets',
|
||||
title: t('xcmWizard.assetsFailed'),
|
||||
description: error instanceof Error ? error.message : t('xcmWizard.assetsFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
@@ -408,8 +402,7 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
const handleTestXCMTransfer = async () => {
|
||||
if (!assetHubApi || !isAssetHubReady || !account || !signer) {
|
||||
toast({
|
||||
title: 'Not Ready',
|
||||
description: 'Please wait for Asset Hub connection',
|
||||
title: t('xcmWizard.notReady'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
return;
|
||||
@@ -428,13 +421,12 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
|
||||
if (result.success) {
|
||||
toast({
|
||||
title: 'XCM Test Successful!',
|
||||
description: `Received ${result.balance} wUSDT`,
|
||||
title: t('xcmWizard.testSuccess', { balance: result.balance }),
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: 'XCM Test Failed',
|
||||
description: result.error || 'Test transfer failed',
|
||||
title: t('xcmWizard.testFailed'),
|
||||
description: result.error || t('xcmWizard.testFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@@ -445,8 +437,8 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
6: { completed: false, error: error instanceof Error ? error.message : 'Unknown error' },
|
||||
}));
|
||||
toast({
|
||||
title: 'Test Failed',
|
||||
description: error instanceof Error ? error.message : 'XCM test failed',
|
||||
title: t('xcmWizard.testFailed'),
|
||||
description: error instanceof Error ? error.message : t('xcmWizard.testFailed'),
|
||||
variant: 'destructive',
|
||||
});
|
||||
} finally {
|
||||
@@ -463,15 +455,15 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Select Relay Chain</Label>
|
||||
<Label>{t('xcmWizard.relayChainLabel')}</Label>
|
||||
<Select value={relayChain} onValueChange={(value: RelayChain) => setRelayChain(value)}>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="westend">Westend (Testnet)</SelectItem>
|
||||
<SelectItem value="rococo">Rococo (Testnet)</SelectItem>
|
||||
<SelectItem value="polkadot">Polkadot (Mainnet)</SelectItem>
|
||||
<SelectItem value="westend">{t('xcmWizard.westend')}</SelectItem>
|
||||
<SelectItem value="rococo">{t('xcmWizard.rococo')}</SelectItem>
|
||||
<SelectItem value="polkadot">{t('xcmWizard.polkadot')}</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -496,15 +488,15 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
{reserving ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Reserving ParaId...
|
||||
{t('xcmWizard.reserving')}
|
||||
</>
|
||||
) : steps[1].completed ? (
|
||||
<>
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
ParaId Reserved
|
||||
{t('xcmWizard.reserved')}
|
||||
</>
|
||||
) : (
|
||||
'Reserve ParaId'
|
||||
t('xcmWizard.reserveBtn')
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -550,15 +542,15 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
{generating ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Generating Artifacts...
|
||||
{t('xcmWizard.generating')}
|
||||
</>
|
||||
) : steps[2].completed ? (
|
||||
<>
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
Artifacts Generated
|
||||
{t('xcmWizard.generated')}
|
||||
</>
|
||||
) : (
|
||||
'Generate Chain Artifacts'
|
||||
t('xcmWizard.generateBtn')
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -568,7 +560,7 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>Upload Genesis State</Label>
|
||||
<Label>{t('xcmWizard.genesisLabel')}</Label>
|
||||
<Input
|
||||
type="file"
|
||||
accept=".hex,.txt"
|
||||
@@ -577,7 +569,7 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Upload Runtime WASM</Label>
|
||||
<Label>{t('xcmWizard.wasmLabel')}</Label>
|
||||
<Input
|
||||
type="file"
|
||||
accept=".wasm"
|
||||
@@ -609,15 +601,15 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
{registering ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Registering Parachain...
|
||||
{t('xcmWizard.registering')}
|
||||
</>
|
||||
) : steps[3].completed ? (
|
||||
<>
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
Parachain Registered
|
||||
{t('xcmWizard.registered')}
|
||||
</>
|
||||
) : (
|
||||
'Register Parachain'
|
||||
t('xcmWizard.registerBtn')
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -657,15 +649,15 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
{openingChannels ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Opening HRMP Channels...
|
||||
{t('xcmWizard.openingHrmp')}
|
||||
</>
|
||||
) : steps[4].completed ? (
|
||||
<>
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
Channels Opened
|
||||
{t('xcmWizard.channelsOpened')}
|
||||
</>
|
||||
) : (
|
||||
'Open HRMP Channels'
|
||||
t('xcmWizard.hrmpBtn')
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -675,7 +667,7 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Register foreign assets: USDT, DOT, and other cross-chain tokens
|
||||
{t('xcmWizard.assetsDesc')}
|
||||
</p>
|
||||
|
||||
{registeredAssets.length > 0 && (
|
||||
@@ -705,15 +697,15 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
{registeringAssets ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Registering Assets...
|
||||
{t('xcmWizard.registeringAssets')}
|
||||
</>
|
||||
) : steps[5].completed ? (
|
||||
<>
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
Assets Registered
|
||||
{t('xcmWizard.assetsRegistered')}
|
||||
</>
|
||||
) : (
|
||||
'Register Foreign Assets'
|
||||
t('xcmWizard.assetsBtn')
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -723,7 +715,7 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Test XCM transfer from Asset Hub to verify bridge functionality
|
||||
{t('xcmWizard.testDesc')}
|
||||
</p>
|
||||
|
||||
{testResult && (
|
||||
@@ -731,8 +723,8 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
{testResult.success ? <CheckCircle className="h-4 w-4" /> : <AlertCircle className="h-4 w-4" />}
|
||||
<AlertDescription>
|
||||
{testResult.success
|
||||
? `Test successful! Balance: ${testResult.balance} wUSDT`
|
||||
: `Test failed: ${testResult.error}`}
|
||||
? t('xcmWizard.testSuccess', { balance: testResult.balance })
|
||||
: testResult.error}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
@@ -748,15 +740,15 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
{testing ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Testing XCM Transfer...
|
||||
{t('xcmWizard.testing')}
|
||||
</>
|
||||
) : steps[6].completed ? (
|
||||
<>
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
XCM Test Passed
|
||||
{t('xcmWizard.testPassed')}
|
||||
</>
|
||||
) : (
|
||||
'Test XCM Transfer'
|
||||
t('xcmWizard.testBtn')
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -773,8 +765,8 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
// Handle Finish Configuration
|
||||
const handleFinishConfiguration = () => {
|
||||
toast({
|
||||
title: 'XCM Configuration Complete!',
|
||||
description: 'Your parachain is fully configured and ready for cross-chain transfers',
|
||||
title: t('xcmWizard.complete'),
|
||||
description: t('xcmWizard.completeDesc'),
|
||||
});
|
||||
|
||||
if (onSuccess) {
|
||||
@@ -792,9 +784,9 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>XCM Configuration Wizard</CardTitle>
|
||||
<CardTitle>{t('xcmWizard.title')}</CardTitle>
|
||||
<CardDescription>
|
||||
Complete parachain setup and cross-chain integration
|
||||
{t('xcmWizard.subtitle')}
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" onClick={onClose}>
|
||||
@@ -805,7 +797,7 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
<div className="mt-4">
|
||||
<Progress value={progress} className="h-2" />
|
||||
<p className="mt-2 text-xs text-muted-foreground text-center">
|
||||
{Object.values(steps).filter(s => s.completed).length} / {totalSteps} steps completed
|
||||
{t('xcmWizard.progress', { completed: Object.values(steps).filter(s => s.completed).length, total: totalSteps })}
|
||||
</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
@@ -842,12 +834,12 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline">Step {currentStep}</Badge>
|
||||
<h3 className="font-semibold">
|
||||
{currentStep === 1 && 'Reserve ParaId'}
|
||||
{currentStep === 2 && 'Generate Chain Artifacts'}
|
||||
{currentStep === 3 && 'Register Parachain'}
|
||||
{currentStep === 4 && 'Open HRMP Channels'}
|
||||
{currentStep === 5 && 'Register Foreign Assets'}
|
||||
{currentStep === 6 && 'Test XCM Transfer'}
|
||||
{currentStep === 1 && t('xcmWizard.stepReserve')}
|
||||
{currentStep === 2 && t('xcmWizard.stepArtifacts')}
|
||||
{currentStep === 3 && t('xcmWizard.stepParachain')}
|
||||
{currentStep === 4 && t('xcmWizard.stepHrmp')}
|
||||
{currentStep === 5 && t('xcmWizard.stepAssets')}
|
||||
{currentStep === 6 && t('xcmWizard.stepTest')}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
@@ -861,20 +853,20 @@ export const XCMConfigurationWizard: React.FC<XCMConfigurationWizardProps> = ({
|
||||
onClick={() => setCurrentStep(Math.max(1, currentStep - 1))}
|
||||
disabled={currentStep === 1}
|
||||
>
|
||||
Previous
|
||||
{t('xcmWizard.previous')}
|
||||
</Button>
|
||||
|
||||
{allStepsCompleted ? (
|
||||
<Button onClick={handleFinishConfiguration} className="bg-kurdish-green hover:bg-kurdish-green-dark">
|
||||
<CheckCircle className="mr-2 h-4 w-4" />
|
||||
Finish Configuration
|
||||
{t('xcmWizard.finish')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => setCurrentStep(Math.min(totalSteps, currentStep + 1))}
|
||||
disabled={currentStep === totalSteps || !steps[currentStep].completed}
|
||||
>
|
||||
Next
|
||||
{t('xcmWizard.next')}
|
||||
<ChevronRight className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user