mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-22 05:37:56 +00:00
4f683538d3
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.
185 lines
6.7 KiB
TypeScript
185 lines
6.7 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Shield, Users, CheckCircle, XCircle, ExternalLink } from 'lucide-react';
|
|
import { Card } from '@/components/ui/card';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
|
import { usePezkuwi } from '@/contexts/PezkuwiContext';
|
|
import {
|
|
getMultisigMemberInfo,
|
|
calculateMultisigAddress,
|
|
USDT_MULTISIG_CONFIG,
|
|
formatMultisigAddress,
|
|
} from '@pezkuwi/lib/multisig';
|
|
import { getTikiDisplayName, getTikiEmoji } from '@pezkuwi/lib/tiki';
|
|
|
|
interface MultisigMembersProps {
|
|
specificAddresses?: Record<string, string>;
|
|
showMultisigAddress?: boolean;
|
|
}
|
|
|
|
interface MultisigMember {
|
|
address: string;
|
|
displayName: string;
|
|
emoji: string;
|
|
role: string;
|
|
isTiki: boolean;
|
|
trustScore?: number;
|
|
balance?: string;
|
|
}
|
|
|
|
export const MultisigMembers: React.FC<MultisigMembersProps> = ({
|
|
specificAddresses = {},
|
|
showMultisigAddress = true,
|
|
}) => {
|
|
const { t } = useTranslation();
|
|
const { api, isApiReady } = usePezkuwi();
|
|
const [members, setMembers] = useState<MultisigMember[]>([]);
|
|
const [multisigAddress, setMultisigAddress] = useState('');
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
if (!api || !isApiReady) return;
|
|
|
|
const fetchMembers = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const memberInfo = await getMultisigMemberInfo(api, specificAddresses);
|
|
setMembers(memberInfo);
|
|
|
|
// Calculate multisig address
|
|
const addresses = memberInfo.map((m) => m.address);
|
|
if (addresses.length > 0) {
|
|
const multisig = calculateMultisigAddress(addresses);
|
|
setMultisigAddress(multisig);
|
|
}
|
|
} catch (error) {
|
|
if (import.meta.env.DEV) console.error('Error fetching multisig members:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchMembers();
|
|
}, [api, isApiReady, specificAddresses]);
|
|
|
|
if (loading) {
|
|
return (
|
|
<Card className="p-6 bg-gray-800/50 border-gray-700">
|
|
<div className="flex items-center justify-center py-8">
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
|
|
</div>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Card className="p-6 bg-gray-800/50 border-gray-700">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between mb-6">
|
|
<div className="flex items-center gap-3">
|
|
<Shield className="h-6 w-6 text-blue-400" />
|
|
<div>
|
|
<h3 className="text-lg font-bold text-white">{t('multisigMembers.title')}</h3>
|
|
<p className="text-sm text-gray-400">
|
|
{t('multisigMembers.threshold', { threshold: USDT_MULTISIG_CONFIG.threshold, total: members.length })}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Badge variant="outline" className="flex items-center gap-1">
|
|
<Users className="h-3 w-3" />
|
|
{t('multisigMembers.members', { count: members.length })}
|
|
</Badge>
|
|
</div>
|
|
|
|
{/* Multisig Address */}
|
|
{showMultisigAddress && multisigAddress && (
|
|
<div className="mb-6 p-4 bg-gray-900/50 rounded-lg">
|
|
<p className="text-xs text-gray-400 mb-2">{t('multisigMembers.account')}</p>
|
|
<div className="flex items-center justify-between">
|
|
<code className="text-sm text-green-400 font-mono">{formatMultisigAddress(multisigAddress)}</code>
|
|
<button
|
|
onClick={() => navigator.clipboard.writeText(multisigAddress)}
|
|
className="text-blue-400 hover:text-blue-300 text-xs"
|
|
>
|
|
{t('multisigMembers.copyFull')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Members List */}
|
|
<div className="space-y-3">
|
|
{members.map((member, index) => (
|
|
<div
|
|
key={index}
|
|
className="flex items-center justify-between p-4 bg-gray-900/30 rounded-lg hover:bg-gray-900/50 transition-colors"
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<div className="flex items-center justify-center w-10 h-10 rounded-full bg-gray-800">
|
|
<span className="text-xl">{getTikiEmoji(member.tiki)}</span>
|
|
</div>
|
|
<div>
|
|
<p className="font-semibold text-white">{member.role}</p>
|
|
<div className="flex items-center gap-2 mt-1">
|
|
<Badge variant="outline" className="text-xs">
|
|
{getTikiDisplayName(member.tiki)}
|
|
</Badge>
|
|
{member.isUnique && (
|
|
<Badge variant="secondary" className="text-xs flex items-center gap-1">
|
|
<CheckCircle className="h-3 w-3" />
|
|
{t('multisigMembers.onChain')}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="text-right">
|
|
<code className="text-xs text-gray-400 font-mono">
|
|
{member.address.slice(0, 6)}...{member.address.slice(-4)}
|
|
</code>
|
|
<div className="flex items-center gap-2 mt-1 justify-end">
|
|
{member.isUnique ? (
|
|
<CheckCircle className="h-4 w-4 text-green-500" title={t('multisigMembers.verified')} />
|
|
) : (
|
|
<XCircle className="h-4 w-4 text-yellow-500" title={t('multisigMembers.specified')} />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Info Alert */}
|
|
<Alert className="mt-6 bg-blue-900/20 border-blue-500">
|
|
<Shield className="h-4 w-4" />
|
|
<AlertDescription>
|
|
<p className="font-semibold mb-1">{t('multisigMembers.securityTitle')}</p>
|
|
<ul className="text-sm space-y-1">
|
|
<li>• {t('multisigMembers.sigRequired', { threshold: USDT_MULTISIG_CONFIG.threshold, total: members.length })}</li>
|
|
<li>• {t('multisigMembers.verifiedOnChain', { count: members.filter(m => m.isUnique).length })}</li>
|
|
<li>• {t('multisigMembers.noSingleControl')}</li>
|
|
<li>• {t('multisigMembers.allVisible')}</li>
|
|
</ul>
|
|
</AlertDescription>
|
|
</Alert>
|
|
|
|
{/* Explorer Link */}
|
|
{multisigAddress && (
|
|
<div className="mt-4 text-center">
|
|
<a
|
|
href={`https://pezkuwichain.io/explorer/accounts/${multisigAddress}`}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="inline-flex items-center gap-2 text-sm text-blue-400 hover:text-blue-300"
|
|
>
|
|
{t('multisigMembers.viewExplorer')}
|
|
<ExternalLink className="h-4 w-4" />
|
|
</a>
|
|
</div>
|
|
)}
|
|
</Card>
|
|
);
|
|
};
|