mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-28 02:38:00 +00:00
feat: Complete wallet features - Multi-token support, Receive modal, Transaction history
This commit is contained in:
+1
-1
@@ -50,4 +50,4 @@
|
||||
|
||||
.read-the-docs {
|
||||
color: #5f7676;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ export const AccountBalance: React.FC = () => {
|
||||
reserved: '0',
|
||||
total: '0',
|
||||
});
|
||||
const [pezBalance, setPezBalance] = useState<string>('0');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const fetchBalance = async () => {
|
||||
@@ -22,12 +23,13 @@ export const AccountBalance: React.FC = () => {
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
// Fetch HEZ balance
|
||||
const { data: balanceData } = await api.query.system.account(selectedAccount.address);
|
||||
|
||||
const free = balanceData.free.toString();
|
||||
const reserved = balanceData.reserved.toString();
|
||||
|
||||
// Convert from plancks to tokens (assuming 12 decimals like DOT)
|
||||
// Convert from plancks to tokens (12 decimals)
|
||||
const decimals = 12;
|
||||
const divisor = Math.pow(10, decimals);
|
||||
|
||||
@@ -40,6 +42,23 @@ export const AccountBalance: React.FC = () => {
|
||||
reserved: reservedTokens,
|
||||
total: totalTokens,
|
||||
});
|
||||
|
||||
// Fetch PEZ balance (Asset ID: 1)
|
||||
try {
|
||||
const pezAssetBalance = await api.query.assets.account(1, selectedAccount.address);
|
||||
|
||||
if (pezAssetBalance.isSome) {
|
||||
const assetData = pezAssetBalance.unwrap();
|
||||
const pezAmount = assetData.balance.toString();
|
||||
const pezTokens = (parseInt(pezAmount) / divisor).toFixed(4);
|
||||
setPezBalance(pezTokens);
|
||||
} else {
|
||||
setPezBalance('0');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch PEZ balance:', error);
|
||||
setPezBalance('0');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch balance:', error);
|
||||
} finally {
|
||||
@@ -50,13 +69,15 @@ export const AccountBalance: React.FC = () => {
|
||||
useEffect(() => {
|
||||
fetchBalance();
|
||||
|
||||
// Subscribe to balance updates
|
||||
let unsubscribe: () => void;
|
||||
// Subscribe to HEZ balance updates
|
||||
let unsubscribeHez: () => void;
|
||||
let unsubscribePez: () => void;
|
||||
|
||||
const subscribeBalance = async () => {
|
||||
if (!api || !isApiReady || !selectedAccount) return;
|
||||
|
||||
unsubscribe = await api.query.system.account(
|
||||
// Subscribe to HEZ balance
|
||||
unsubscribeHez = await api.query.system.account(
|
||||
selectedAccount.address,
|
||||
({ data: balanceData }) => {
|
||||
const free = balanceData.free.toString();
|
||||
@@ -76,12 +97,35 @@ export const AccountBalance: React.FC = () => {
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Subscribe to PEZ balance (Asset ID: 1)
|
||||
try {
|
||||
unsubscribePez = await api.query.assets.account(
|
||||
1,
|
||||
selectedAccount.address,
|
||||
(assetBalance) => {
|
||||
if (assetBalance.isSome) {
|
||||
const assetData = assetBalance.unwrap();
|
||||
const pezAmount = assetData.balance.toString();
|
||||
const decimals = 12;
|
||||
const divisor = Math.pow(10, decimals);
|
||||
const pezTokens = (parseInt(pezAmount) / divisor).toFixed(4);
|
||||
setPezBalance(pezTokens);
|
||||
} else {
|
||||
setPezBalance('0');
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to subscribe to PEZ balance:', error);
|
||||
}
|
||||
};
|
||||
|
||||
subscribeBalance();
|
||||
|
||||
return () => {
|
||||
if (unsubscribe) unsubscribe();
|
||||
if (unsubscribeHez) unsubscribeHez();
|
||||
if (unsubscribePez) unsubscribePez();
|
||||
};
|
||||
}, [api, isApiReady, selectedAccount]);
|
||||
|
||||
@@ -100,12 +144,12 @@ export const AccountBalance: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Total Balance Card */}
|
||||
{/* HEZ Balance Card */}
|
||||
<Card className="bg-gradient-to-br from-green-900/30 to-yellow-900/30 border-green-500/30">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-lg font-medium text-gray-300">
|
||||
Total Balance
|
||||
HEZ Balance
|
||||
</CardTitle>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -155,6 +199,26 @@ export const AccountBalance: React.FC = () => {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* PEZ Balance Card */}
|
||||
<Card className="bg-gradient-to-br from-blue-900/30 to-purple-900/30 border-blue-500/30">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-lg font-medium text-gray-300">
|
||||
PEZ Token Balance
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div>
|
||||
<div className="text-4xl font-bold text-white mb-1">
|
||||
{isLoading ? '...' : pezBalance}
|
||||
<span className="text-2xl text-gray-400 ml-2">PEZ</span>
|
||||
</div>
|
||||
<div className="text-sm text-gray-400">
|
||||
Governance & Rewards Token
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Account Info */}
|
||||
<Card className="bg-gray-900 border-gray-800">
|
||||
<CardContent className="pt-6">
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import React, { useState } from 'react';
|
||||
import { usePolkadot } from '@/contexts/PolkadotContext';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Copy, CheckCircle, QrCode } from 'lucide-react';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
interface ReceiveModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const ReceiveModal: React.FC<ReceiveModalProps> = ({ isOpen, onClose }) => {
|
||||
const { selectedAccount } = usePolkadot();
|
||||
const { toast } = useToast();
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [qrCodeDataUrl, setQrCodeDataUrl] = useState<string>('');
|
||||
|
||||
React.useEffect(() => {
|
||||
if (selectedAccount && isOpen) {
|
||||
// Generate QR code
|
||||
QRCode.toDataURL(selectedAccount.address, {
|
||||
width: 300,
|
||||
margin: 2,
|
||||
color: {
|
||||
dark: '#ffffff',
|
||||
light: '#0f172a'
|
||||
}
|
||||
}).then(setQrCodeDataUrl).catch(console.error);
|
||||
}
|
||||
}, [selectedAccount, isOpen]);
|
||||
|
||||
const handleCopyAddress = async () => {
|
||||
if (!selectedAccount) return;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(selectedAccount.address);
|
||||
setCopied(true);
|
||||
toast({
|
||||
title: "Address Copied!",
|
||||
description: "Your wallet address has been copied to clipboard",
|
||||
});
|
||||
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "Copy Failed",
|
||||
description: "Failed to copy address to clipboard",
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (!selectedAccount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="bg-gray-900 border-gray-800 max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-white">Receive Tokens</DialogTitle>
|
||||
<DialogDescription className="text-gray-400">
|
||||
Share this address to receive HEZ, PEZ, and other tokens
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* QR Code */}
|
||||
<div className="bg-white rounded-lg p-4 mx-auto w-fit">
|
||||
{qrCodeDataUrl ? (
|
||||
<img src={qrCodeDataUrl} alt="QR Code" className="w-64 h-64" />
|
||||
) : (
|
||||
<div className="w-64 h-64 flex items-center justify-center bg-gray-200">
|
||||
<QrCode className="w-16 h-16 text-gray-400 animate-pulse" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Account Name */}
|
||||
<div className="text-center">
|
||||
<div className="text-sm text-gray-400 mb-1">Account Name</div>
|
||||
<div className="text-xl font-semibold text-white">
|
||||
{selectedAccount.meta.name || 'Unnamed Account'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Address */}
|
||||
<div className="bg-gray-800/50 rounded-lg p-4">
|
||||
<div className="text-sm text-gray-400 mb-2">Wallet Address</div>
|
||||
<div className="bg-gray-900 rounded p-3 mb-3">
|
||||
<div className="text-white font-mono text-sm break-all">
|
||||
{selectedAccount.address}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={handleCopyAddress}
|
||||
className="w-full bg-gradient-to-r from-green-600 to-yellow-400 hover:from-green-700 hover:to-yellow-500"
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<CheckCircle className="w-4 h-4 mr-2" />
|
||||
Copied!
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="w-4 h-4 mr-2" />
|
||||
Copy Address
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Warning */}
|
||||
<div className="bg-yellow-500/10 border border-yellow-500/30 rounded-lg p-3">
|
||||
<p className="text-yellow-400 text-xs">
|
||||
<strong>Important:</strong> Only send PezkuwiChain compatible tokens to this address. Sending other tokens may result in permanent loss.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,289 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { usePolkadot } from '@/contexts/PolkadotContext';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { History, ExternalLink, ArrowUpRight, ArrowDownRight, RefreshCw } from 'lucide-react';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
|
||||
interface TransactionHistoryProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface Transaction {
|
||||
blockNumber: number;
|
||||
extrinsicIndex: number;
|
||||
hash: string;
|
||||
method: string;
|
||||
section: string;
|
||||
from: string;
|
||||
to?: string;
|
||||
amount?: string;
|
||||
success: boolean;
|
||||
timestamp?: number;
|
||||
}
|
||||
|
||||
export const TransactionHistory: React.FC<TransactionHistoryProps> = ({ isOpen, onClose }) => {
|
||||
const { api, isApiReady, selectedAccount } = usePolkadot();
|
||||
const { toast } = useToast();
|
||||
const [transactions, setTransactions] = useState<Transaction[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const fetchTransactions = async () => {
|
||||
if (!api || !isApiReady || !selectedAccount) return;
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
console.log('Fetching transactions...');
|
||||
const currentBlock = await api.rpc.chain.getBlock();
|
||||
const currentBlockNumber = currentBlock.block.header.number.toNumber();
|
||||
|
||||
console.log('Current block number:', currentBlockNumber);
|
||||
|
||||
const txList: Transaction[] = [];
|
||||
const blocksToCheck = Math.min(200, currentBlockNumber);
|
||||
|
||||
for (let i = 0; i < blocksToCheck && txList.length < 20; i++) {
|
||||
const blockNumber = currentBlockNumber - i;
|
||||
|
||||
try {
|
||||
const blockHash = await api.rpc.chain.getBlockHash(blockNumber);
|
||||
const block = await api.rpc.chain.getBlock(blockHash);
|
||||
|
||||
// Try to get timestamp, but don't fail if state is pruned
|
||||
let timestamp = 0;
|
||||
try {
|
||||
const ts = await api.query.timestamp.now.at(blockHash);
|
||||
timestamp = ts.toNumber();
|
||||
} catch (error) {
|
||||
// State pruned, use current time as fallback
|
||||
timestamp = Date.now();
|
||||
}
|
||||
|
||||
console.log(`Block #${blockNumber}: ${block.block.extrinsics.length} extrinsics`);
|
||||
|
||||
// Check each extrinsic in the block
|
||||
block.block.extrinsics.forEach((extrinsic, index) => {
|
||||
// Skip unsigned extrinsics (system calls)
|
||||
if (!extrinsic.isSigned) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { method, signer } = extrinsic;
|
||||
|
||||
console.log(` Extrinsic #${index}: ${method.section}.${method.method}, signer: ${signer.toString()}`);
|
||||
|
||||
// Check if transaction involves our account
|
||||
const fromAddress = signer.toString();
|
||||
const isFromOurAccount = fromAddress === selectedAccount.address;
|
||||
|
||||
// Parse balances.transfer or balances.transferKeepAlive
|
||||
if (method.section === 'balances' &&
|
||||
(method.method === 'transfer' || method.method === 'transferKeepAlive')) {
|
||||
const [dest, value] = method.args;
|
||||
const toAddress = dest.toString();
|
||||
const isToOurAccount = toAddress === selectedAccount.address;
|
||||
|
||||
if (isFromOurAccount || isToOurAccount) {
|
||||
txList.push({
|
||||
blockNumber,
|
||||
extrinsicIndex: index,
|
||||
hash: extrinsic.hash.toHex(),
|
||||
method: method.method,
|
||||
section: method.section,
|
||||
from: fromAddress,
|
||||
to: toAddress,
|
||||
amount: value.toString(),
|
||||
success: true,
|
||||
timestamp: timestamp,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Parse assets.transfer (PEZ, USDT, etc.)
|
||||
if (method.section === 'assets' && method.method === 'transfer') {
|
||||
const [assetId, dest, value] = method.args;
|
||||
const toAddress = dest.toString();
|
||||
const isToOurAccount = toAddress === selectedAccount.address;
|
||||
|
||||
if (isFromOurAccount || isToOurAccount) {
|
||||
txList.push({
|
||||
blockNumber,
|
||||
extrinsicIndex: index,
|
||||
hash: extrinsic.hash.toHex(),
|
||||
method: `${method.method} (Asset ${assetId.toString()})`,
|
||||
section: method.section,
|
||||
from: fromAddress,
|
||||
to: toAddress,
|
||||
amount: value.toString(),
|
||||
success: true,
|
||||
timestamp: timestamp,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (blockError) {
|
||||
console.warn(`Error processing block #${blockNumber}:`, blockError);
|
||||
// Continue to next block
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Found transactions:', txList.length);
|
||||
|
||||
setTransactions(txList);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch transactions:', error);
|
||||
toast({
|
||||
title: "Error",
|
||||
description: "Failed to fetch transaction history",
|
||||
variant: "destructive",
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
fetchTransactions();
|
||||
}
|
||||
}, [isOpen, api, isApiReady, selectedAccount]);
|
||||
|
||||
const formatAmount = (amount: string, decimals: number = 12) => {
|
||||
const value = parseInt(amount) / Math.pow(10, decimals);
|
||||
return value.toFixed(4);
|
||||
};
|
||||
|
||||
const formatTimestamp = (timestamp?: number) => {
|
||||
if (!timestamp) return 'Unknown';
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleString();
|
||||
};
|
||||
|
||||
const isIncoming = (tx: Transaction) => {
|
||||
return tx.to === selectedAccount?.address;
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="bg-gray-900 border-gray-800 max-w-3xl max-h-[80vh]">
|
||||
<DialogHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<DialogTitle className="text-white">Transaction History</DialogTitle>
|
||||
<DialogDescription className="text-gray-400">
|
||||
Recent transactions involving your account
|
||||
</DialogDescription>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={fetchTransactions}
|
||||
disabled={isLoading}
|
||||
className="text-gray-400 hover:text-white"
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${isLoading ? 'animate-spin' : ''}`} />
|
||||
</Button>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-3 overflow-y-auto max-h-[500px]">
|
||||
{isLoading ? (
|
||||
<div className="text-center py-12">
|
||||
<RefreshCw className="w-12 h-12 text-gray-600 mx-auto mb-3 animate-spin" />
|
||||
<p className="text-gray-400">Loading transactions...</p>
|
||||
</div>
|
||||
) : transactions.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<History className="w-12 h-12 text-gray-600 mx-auto mb-3" />
|
||||
<p className="text-gray-500">No transactions found</p>
|
||||
<p className="text-gray-600 text-sm mt-1">
|
||||
Your recent transactions will appear here
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
transactions.map((tx, index) => (
|
||||
<div
|
||||
key={`${tx.blockNumber}-${tx.extrinsicIndex}`}
|
||||
className="bg-gray-800/50 border border-gray-700 rounded-lg p-4 hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{isIncoming(tx) ? (
|
||||
<div className="bg-green-500/20 p-2 rounded-lg">
|
||||
<ArrowDownRight className="w-4 h-4 text-green-400" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="bg-yellow-500/20 p-2 rounded-lg">
|
||||
<ArrowUpRight className="w-4 h-4 text-yellow-400" />
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<div className="text-white font-semibold">
|
||||
{isIncoming(tx) ? 'Received' : 'Sent'}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">
|
||||
{tx.section}.{tx.method}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-white font-mono">
|
||||
{isIncoming(tx) ? '+' : '-'}{formatAmount(tx.amount || '0')}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">
|
||||
Block #{tx.blockNumber}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 text-xs">
|
||||
<div>
|
||||
<span className="text-gray-500">From:</span>
|
||||
<div className="text-gray-300 font-mono">
|
||||
{tx.from.slice(0, 8)}...{tx.from.slice(-6)}
|
||||
</div>
|
||||
</div>
|
||||
{tx.to && (
|
||||
<div>
|
||||
<span className="text-gray-500">To:</span>
|
||||
<div className="text-gray-300 font-mono">
|
||||
{tx.to.slice(0, 8)}...{tx.to.slice(-6)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between mt-2 pt-2 border-t border-gray-700">
|
||||
<div className="text-xs text-gray-500">
|
||||
{formatTimestamp(tx.timestamp)}
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-xs text-blue-400 hover:text-blue-300"
|
||||
onClick={() => {
|
||||
toast({
|
||||
title: "Transaction Details",
|
||||
description: `Block #${tx.blockNumber}, Extrinsic #${tx.extrinsicIndex}`,
|
||||
});
|
||||
}}
|
||||
>
|
||||
View Details
|
||||
<ExternalLink className="w-3 h-3 ml-1" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -10,6 +10,13 @@ import {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { ArrowRight, Loader2, CheckCircle, XCircle } from 'lucide-react';
|
||||
import { useToast } from '@/hooks/use-toast';
|
||||
|
||||
@@ -18,16 +25,38 @@ interface TransferModalProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
type TokenType = 'HEZ' | 'PEZ' | 'USDT' | 'BTC' | 'ETH' | 'DOT';
|
||||
|
||||
interface Token {
|
||||
symbol: TokenType;
|
||||
name: string;
|
||||
assetId?: number;
|
||||
decimals: number;
|
||||
color: string;
|
||||
}
|
||||
|
||||
const TOKENS: Token[] = [
|
||||
{ symbol: 'HEZ', name: 'Hez Token', decimals: 12, color: 'from-green-600 to-yellow-400' },
|
||||
{ symbol: 'PEZ', name: 'Pez Token', assetId: 1, decimals: 12, color: 'from-blue-600 to-purple-400' },
|
||||
{ symbol: 'USDT', name: 'Tether USD', assetId: 2, decimals: 6, color: 'from-green-500 to-green-600' },
|
||||
{ symbol: 'BTC', name: 'Bitcoin', assetId: 3, decimals: 8, color: 'from-orange-500 to-yellow-500' },
|
||||
{ symbol: 'ETH', name: 'Ethereum', assetId: 4, decimals: 18, color: 'from-purple-500 to-blue-500' },
|
||||
{ symbol: 'DOT', name: 'Polkadot', assetId: 5, decimals: 10, color: 'from-pink-500 to-red-500' },
|
||||
];
|
||||
|
||||
export const TransferModal: React.FC<TransferModalProps> = ({ isOpen, onClose }) => {
|
||||
const { api, isApiReady, selectedAccount } = usePolkadot();
|
||||
const { toast } = useToast();
|
||||
|
||||
const [selectedToken, setSelectedToken] = useState<TokenType>('HEZ');
|
||||
const [recipient, setRecipient] = useState('');
|
||||
const [amount, setAmount] = useState('');
|
||||
const [isTransferring, setIsTransferring] = useState(false);
|
||||
const [txStatus, setTxStatus] = useState<'idle' | 'signing' | 'pending' | 'success' | 'error'>('idle');
|
||||
const [txHash, setTxHash] = useState('');
|
||||
|
||||
const currentToken = TOKENS.find(t => t.symbol === selectedToken) || TOKENS[0];
|
||||
|
||||
const handleTransfer = async () => {
|
||||
if (!api || !isApiReady || !selectedAccount) {
|
||||
toast({
|
||||
@@ -55,12 +84,22 @@ export const TransferModal: React.FC<TransferModalProps> = ({ isOpen, onClose })
|
||||
const { web3FromAddress } = await import('@polkadot/extension-dapp');
|
||||
const injector = await web3FromAddress(selectedAccount.address);
|
||||
|
||||
// Convert amount to plancks (12 decimals)
|
||||
const decimals = 12;
|
||||
const amountInPlancks = BigInt(parseFloat(amount) * Math.pow(10, decimals));
|
||||
// Convert amount to smallest unit
|
||||
const amountInSmallestUnit = BigInt(parseFloat(amount) * Math.pow(10, currentToken.decimals));
|
||||
|
||||
// Create transfer transaction
|
||||
const transfer = api.tx.balances.transferKeepAlive(recipient, amountInPlancks.toString());
|
||||
let transfer;
|
||||
|
||||
// Create appropriate transfer transaction based on token type
|
||||
if (selectedToken === 'HEZ') {
|
||||
// Native token transfer
|
||||
transfer = api.tx.balances.transferKeepAlive(recipient, amountInSmallestUnit.toString());
|
||||
} else {
|
||||
// Asset token transfer (PEZ, USDT, BTC, ETH, DOT)
|
||||
if (!currentToken.assetId) {
|
||||
throw new Error('Asset ID not configured');
|
||||
}
|
||||
transfer = api.tx.assets.transfer(currentToken.assetId, recipient, amountInSmallestUnit.toString());
|
||||
}
|
||||
|
||||
setTxStatus('pending');
|
||||
|
||||
@@ -96,7 +135,7 @@ export const TransferModal: React.FC<TransferModalProps> = ({ isOpen, onClose })
|
||||
setTxStatus('success');
|
||||
toast({
|
||||
title: "Transfer Successful!",
|
||||
description: `Sent ${amount} HEZ to ${recipient.slice(0, 8)}...${recipient.slice(-6)}`,
|
||||
description: `Sent ${amount} ${selectedToken} to ${recipient.slice(0, 8)}...${recipient.slice(-6)}`,
|
||||
});
|
||||
|
||||
// Reset form after 2 seconds
|
||||
@@ -133,6 +172,7 @@ export const TransferModal: React.FC<TransferModalProps> = ({ isOpen, onClose })
|
||||
setAmount('');
|
||||
setTxStatus('idle');
|
||||
setTxHash('');
|
||||
setSelectedToken('HEZ');
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
@@ -141,9 +181,9 @@ export const TransferModal: React.FC<TransferModalProps> = ({ isOpen, onClose })
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="bg-gray-900 border-gray-800">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-white">Send HEZ</DialogTitle>
|
||||
<DialogTitle className="text-white">Send Tokens</DialogTitle>
|
||||
<DialogDescription className="text-gray-400">
|
||||
Transfer HEZ tokens to another account
|
||||
Transfer tokens to another account
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@@ -175,6 +215,31 @@ export const TransferModal: React.FC<TransferModalProps> = ({ isOpen, onClose })
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{/* Token Selection */}
|
||||
<div>
|
||||
<Label htmlFor="token" className="text-white">Select Token</Label>
|
||||
<Select value={selectedToken} onValueChange={(value) => setSelectedToken(value as TokenType)} disabled={isTransferring}>
|
||||
<SelectTrigger className="bg-gray-800 border-gray-700 text-white mt-2">
|
||||
<SelectValue placeholder="Select token" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-gray-800 border-gray-700">
|
||||
{TOKENS.map((token) => (
|
||||
<SelectItem
|
||||
key={token.symbol}
|
||||
value={token.symbol}
|
||||
className="text-white hover:bg-gray-700 focus:bg-gray-700"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`w-3 h-3 rounded-full bg-gradient-to-r ${token.color}`}></div>
|
||||
<span className="font-semibold">{token.symbol}</span>
|
||||
<span className="text-gray-400 text-sm">- {token.name}</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="recipient" className="text-white">Recipient Address</Label>
|
||||
<Input
|
||||
@@ -188,17 +253,20 @@ export const TransferModal: React.FC<TransferModalProps> = ({ isOpen, onClose })
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="amount" className="text-white">Amount (HEZ)</Label>
|
||||
<Label htmlFor="amount" className="text-white">Amount ({selectedToken})</Label>
|
||||
<Input
|
||||
id="amount"
|
||||
type="number"
|
||||
step="0.0001"
|
||||
step={selectedToken === 'HEZ' || selectedToken === 'PEZ' ? '0.0001' : '0.000001'}
|
||||
value={amount}
|
||||
onChange={(e) => setAmount(e.target.value)}
|
||||
placeholder="0.0000"
|
||||
className="bg-gray-800 border-gray-700 text-white mt-2"
|
||||
disabled={isTransferring}
|
||||
/>
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
Decimals: {currentToken.decimals}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{txStatus === 'signing' && (
|
||||
@@ -221,7 +289,7 @@ export const TransferModal: React.FC<TransferModalProps> = ({ isOpen, onClose })
|
||||
<Button
|
||||
onClick={handleTransfer}
|
||||
disabled={isTransferring || !recipient || !amount}
|
||||
className="w-full bg-gradient-to-r from-green-600 to-yellow-400 hover:from-green-700 hover:to-yellow-500"
|
||||
className={`w-full bg-gradient-to-r ${currentToken.color} hover:opacity-90`}
|
||||
>
|
||||
{isTransferring ? (
|
||||
<>
|
||||
@@ -230,7 +298,7 @@ export const TransferModal: React.FC<TransferModalProps> = ({ isOpen, onClose })
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Send
|
||||
Send {selectedToken}
|
||||
<ArrowRight className="w-4 h-4 ml-2" />
|
||||
</>
|
||||
)}
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,123 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=JetBrains+Mono:wght@100..800&display=swap');
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
/* Kurdish color scheme - kesk u sor u zer */
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 0 0% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 0 0% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 0 0% 3.9%;
|
||||
--primary: 148 100% 32%; /* Kurdish green */
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 358 84% 52%; /* Kurdish red */
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 52 100% 50%; /* Kurdish yellow muted */
|
||||
--muted-foreground: 0 0% 20%;
|
||||
--accent: 52 100% 50%; /* Kurdish yellow */
|
||||
--accent-foreground: 0 0% 9%;
|
||||
--destructive: 358 84% 52%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 89.8%;
|
||||
--input: 0 0% 89.8%;
|
||||
--ring: 148 100% 32%;
|
||||
--radius: 0.5rem;
|
||||
--chart-1: 148 100% 32%;
|
||||
--chart-2: 358 84% 52%;
|
||||
--chart-3: 52 100% 50%;
|
||||
--chart-4: 148 100% 25%;
|
||||
--chart-5: 358 84% 40%;
|
||||
--sidebar-background: 0 0% 98%;
|
||||
--sidebar-foreground: 0 0% 3.9%;
|
||||
--sidebar-primary: 148 100% 32%;
|
||||
--sidebar-primary-foreground: 0 0% 98%;
|
||||
--sidebar-accent: 52 100% 50%;
|
||||
--sidebar-accent-foreground: 0 0% 9%;
|
||||
--sidebar-border: 0 0% 89.8%;
|
||||
--sidebar-ring: 148 100% 32%;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 0 0% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 0 0% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 0 0% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 148 100% 40%; /* Kurdish green dark */
|
||||
--primary-foreground: 0 0% 9%;
|
||||
--secondary: 358 84% 60%; /* Kurdish red dark */
|
||||
--secondary-foreground: 0 0% 9%;
|
||||
--muted: 52 100% 30%; /* Kurdish yellow dark muted */
|
||||
--muted-foreground: 0 0% 98%;
|
||||
--accent: 52 100% 45%; /* Kurdish yellow dark */
|
||||
--accent-foreground: 0 0% 9%;
|
||||
--destructive: 358 84% 52%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 14.9%;
|
||||
--input: 0 0% 14.9%;
|
||||
--ring: 148 100% 40%;
|
||||
--chart-1: 148 100% 40%;
|
||||
--chart-2: 358 84% 60%;
|
||||
--chart-3: 52 100% 45%;
|
||||
--chart-4: 148 100% 30%;
|
||||
--chart-5: 358 84% 50%;
|
||||
--sidebar-background: 0 0% 7%;
|
||||
--sidebar-foreground: 0 0% 98%;
|
||||
--sidebar-primary: 148 100% 40%;
|
||||
--sidebar-primary-foreground: 0 0% 9%;
|
||||
--sidebar-accent: 52 100% 45%;
|
||||
--sidebar-accent-foreground: 0 0% 9%;
|
||||
--sidebar-border: 0 0% 14.9%;
|
||||
--sidebar-ring: 148 100% 40%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground font-sans dark:bg-background dark:text-foreground;
|
||||
}
|
||||
|
||||
pre, code {
|
||||
@apply font-mono;
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-editor {
|
||||
@apply font-mono text-base leading-relaxed;
|
||||
}
|
||||
|
||||
.markdown-preview {
|
||||
@apply prose max-w-none prose-blue dark:prose-invert;
|
||||
}
|
||||
|
||||
.markdown-preview pre {
|
||||
@apply bg-secondary p-4 rounded-md overflow-x-auto;
|
||||
}
|
||||
|
||||
.markdown-preview code {
|
||||
@apply text-sm font-mono text-primary;
|
||||
}
|
||||
|
||||
.markdown-preview h1,
|
||||
.markdown-preview h2,
|
||||
.markdown-preview h3,
|
||||
.markdown-preview h4,
|
||||
.markdown-preview h5,
|
||||
.markdown-preview h6 {
|
||||
@apply font-sans font-semibold text-foreground;
|
||||
}
|
||||
|
||||
.markdown-preview ul,
|
||||
.markdown-preview ol {
|
||||
@apply my-4 ml-6;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
export default {
|
||||
// Navigation
|
||||
'nav.home': 'الرئيسية',
|
||||
'nav.dashboard': 'لوحة التحكم',
|
||||
'nav.governance': 'الحوكمة',
|
||||
'nav.treasury': 'الخزينة',
|
||||
'nav.staking': 'التخزين',
|
||||
'nav.forum': 'المنتدى',
|
||||
'nav.profile': 'الملف الشخصي',
|
||||
'nav.admin': 'المدير',
|
||||
|
||||
// Hero Section
|
||||
'hero.title': 'منصة حوكمة البلوكشين',
|
||||
'hero.subtitle': 'حوكمة ديمقراطية وشفافة بتقنية البلوكشين',
|
||||
'hero.cta.primary': 'ابدأ الآن',
|
||||
'hero.cta.secondary': 'اعرف المزيد',
|
||||
|
||||
// Auth
|
||||
'auth.login': 'تسجيل الدخول',
|
||||
'auth.logout': 'تسجيل الخروج',
|
||||
'auth.signup': 'إنشاء حساب',
|
||||
'auth.email': 'البريد الإلكتروني',
|
||||
'auth.password': 'كلمة المرور',
|
||||
'auth.confirmPassword': 'تأكيد كلمة المرور',
|
||||
'auth.rememberMe': 'تذكرني',
|
||||
'auth.forgotPassword': 'نسيت كلمة المرور؟',
|
||||
|
||||
// Wallet
|
||||
'wallet.connect': 'ربط المحفظة',
|
||||
'wallet.disconnect': 'قطع الاتصال',
|
||||
'wallet.balance': 'الرصيد',
|
||||
'wallet.address': 'العنوان',
|
||||
'wallet.network': 'الشبكة',
|
||||
|
||||
// Governance
|
||||
'governance.proposals': 'المقترحات',
|
||||
'governance.activeProposals': 'المقترحات النشطة',
|
||||
'governance.vote': 'التصويت',
|
||||
'governance.delegate': 'التفويض',
|
||||
'governance.createProposal': 'إنشاء مقترح',
|
||||
'governance.votingPower': 'قوة التصويت',
|
||||
|
||||
// Common
|
||||
'common.loading': 'جاري التحميل...',
|
||||
'common.save': 'حفظ',
|
||||
'common.cancel': 'إلغاء',
|
||||
'common.confirm': 'تأكيد',
|
||||
'common.delete': 'حذف',
|
||||
'common.edit': 'تعديل',
|
||||
'common.search': 'بحث',
|
||||
'common.filter': 'تصفية',
|
||||
'common.sort': 'ترتيب',
|
||||
'common.submit': 'إرسال',
|
||||
'common.back': 'رجوع',
|
||||
'common.next': 'التالي',
|
||||
'common.previous': 'السابق',
|
||||
'common.yes': 'نعم',
|
||||
'common.no': 'لا'
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
export default {
|
||||
// Navigation
|
||||
'nav.home': 'سەرەتا',
|
||||
'nav.dashboard': 'داشبۆرد',
|
||||
'nav.governance': 'حوکمڕانی',
|
||||
'nav.treasury': 'خەزێنە',
|
||||
'nav.staking': 'ستەیکینگ',
|
||||
'nav.forum': 'فۆرەم',
|
||||
'nav.profile': 'پرۆفایل',
|
||||
'nav.admin': 'بەڕێوەبەر',
|
||||
|
||||
// Hero Section
|
||||
'hero.title': 'پلاتفۆرمی حوکمڕانی بلۆکچەین',
|
||||
'hero.subtitle': 'حوکمڕانی دیموکراتی و شەفاف بە تەکنەلۆژیای بلۆکچەین',
|
||||
'hero.cta.primary': 'دەست پێ بکە',
|
||||
'hero.cta.secondary': 'زیاتر بزانە',
|
||||
|
||||
// Auth
|
||||
'auth.login': 'چوونەژوورەوە',
|
||||
'auth.logout': 'دەرچوون',
|
||||
'auth.signup': 'تۆمارکردن',
|
||||
'auth.email': 'ئیمەیڵ',
|
||||
'auth.password': 'وشەی نهێنی',
|
||||
'auth.confirmPassword': 'دووبارەکردنەوەی وشەی نهێنی',
|
||||
'auth.rememberMe': 'بمهێنەوە یاد',
|
||||
'auth.forgotPassword': 'وشەی نهێنیت لەبیر چووە؟',
|
||||
|
||||
// Wallet
|
||||
'wallet.connect': 'جزدان بەستنەوە',
|
||||
'wallet.disconnect': 'پچڕاندن',
|
||||
'wallet.balance': 'باڵانس',
|
||||
'wallet.address': 'ناونیشان',
|
||||
'wallet.network': 'تۆڕ',
|
||||
|
||||
// Governance
|
||||
'governance.proposals': 'پێشنیارەکان',
|
||||
'governance.activeProposals': 'پێشنیارە چالاکەکان',
|
||||
'governance.vote': 'دەنگدان',
|
||||
'governance.delegate': 'نوێنەر',
|
||||
'governance.createProposal': 'پێشنیار دروست بکە',
|
||||
'governance.votingPower': 'هێزی دەنگدان',
|
||||
|
||||
// Common
|
||||
'common.loading': 'چاوەڕوان بە...',
|
||||
'common.save': 'پاشەکەوتکردن',
|
||||
'common.cancel': 'هەڵوەشاندنەوە',
|
||||
'common.confirm': 'دڵنیاکردنەوە',
|
||||
'common.delete': 'سڕینەوە',
|
||||
'common.edit': 'دەستکاریکردن',
|
||||
'common.search': 'گەڕان',
|
||||
'common.filter': 'فلتەر',
|
||||
'common.sort': 'ڕیزکردن',
|
||||
'common.submit': 'ناردن',
|
||||
'common.back': 'گەڕانەوە',
|
||||
'common.next': 'دواتر',
|
||||
'common.previous': 'پێشوو',
|
||||
'common.yes': 'بەڵێ',
|
||||
'common.no': 'نەخێر'
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
export default {
|
||||
// Navigation
|
||||
'nav.home': 'Home',
|
||||
'nav.dashboard': 'Dashboard',
|
||||
'nav.governance': 'Governance',
|
||||
'nav.treasury': 'Treasury',
|
||||
'nav.staking': 'Staking',
|
||||
'nav.forum': 'Forum',
|
||||
'nav.profile': 'Profile',
|
||||
'nav.admin': 'Admin',
|
||||
|
||||
// Hero Section
|
||||
'hero.title': 'Blockchain Governance Platform',
|
||||
'hero.subtitle': 'Democratic and transparent governance with blockchain technology',
|
||||
'hero.cta.primary': 'Get Started',
|
||||
'hero.cta.secondary': 'Learn More',
|
||||
|
||||
// Auth
|
||||
'auth.login': 'Login',
|
||||
'auth.logout': 'Logout',
|
||||
'auth.signup': 'Sign Up',
|
||||
'auth.email': 'Email',
|
||||
'auth.password': 'Password',
|
||||
'auth.confirmPassword': 'Confirm Password',
|
||||
'auth.rememberMe': 'Remember me',
|
||||
'auth.forgotPassword': 'Forgot password?',
|
||||
|
||||
// Wallet
|
||||
'wallet.connect': 'Connect Wallet',
|
||||
'wallet.disconnect': 'Disconnect',
|
||||
'wallet.balance': 'Balance',
|
||||
'wallet.address': 'Address',
|
||||
'wallet.network': 'Network',
|
||||
|
||||
// Governance
|
||||
'governance.proposals': 'Proposals',
|
||||
'governance.activeProposals': 'Active Proposals',
|
||||
'governance.vote': 'Vote',
|
||||
'governance.delegate': 'Delegate',
|
||||
'governance.createProposal': 'Create Proposal',
|
||||
'governance.votingPower': 'Voting Power',
|
||||
|
||||
// Common
|
||||
'common.loading': 'Loading...',
|
||||
'common.save': 'Save',
|
||||
'common.cancel': 'Cancel',
|
||||
'common.confirm': 'Confirm',
|
||||
'common.delete': 'Delete',
|
||||
'common.edit': 'Edit',
|
||||
'common.search': 'Search',
|
||||
'common.filter': 'Filter',
|
||||
'common.sort': 'Sort',
|
||||
'common.submit': 'Submit',
|
||||
'common.back': 'Back',
|
||||
'common.next': 'Next',
|
||||
'common.previous': 'Previous',
|
||||
'common.yes': 'Yes',
|
||||
'common.no': 'No'
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
export default {
|
||||
// Navigation
|
||||
'nav.home': 'خانه',
|
||||
'nav.dashboard': 'داشبورد',
|
||||
'nav.governance': 'حکمرانی',
|
||||
'nav.treasury': 'خزانه',
|
||||
'nav.staking': 'استیکینگ',
|
||||
'nav.forum': 'انجمن',
|
||||
'nav.profile': 'پروفایل',
|
||||
'nav.admin': 'مدیر',
|
||||
|
||||
// Hero Section
|
||||
'hero.title': 'پلتفرم حکمرانی بلاکچین',
|
||||
'hero.subtitle': 'حکمرانی دموکراتیک و شفاف با فناوری بلاکچین',
|
||||
'hero.cta.primary': 'شروع کنید',
|
||||
'hero.cta.secondary': 'بیشتر بدانید',
|
||||
|
||||
// Auth
|
||||
'auth.login': 'ورود',
|
||||
'auth.logout': 'خروج',
|
||||
'auth.signup': 'ثبت نام',
|
||||
'auth.email': 'ایمیل',
|
||||
'auth.password': 'رمز عبور',
|
||||
'auth.confirmPassword': 'تایید رمز عبور',
|
||||
'auth.rememberMe': 'مرا به خاطر بسپار',
|
||||
'auth.forgotPassword': 'رمز عبور را فراموش کردهاید؟',
|
||||
|
||||
// Wallet
|
||||
'wallet.connect': 'اتصال کیف پول',
|
||||
'wallet.disconnect': 'قطع اتصال',
|
||||
'wallet.balance': 'موجودی',
|
||||
'wallet.address': 'آدرس',
|
||||
'wallet.network': 'شبکه',
|
||||
|
||||
// Governance
|
||||
'governance.proposals': 'پیشنهادات',
|
||||
'governance.activeProposals': 'پیشنهادات فعال',
|
||||
'governance.vote': 'رای دادن',
|
||||
'governance.delegate': 'نماینده',
|
||||
'governance.createProposal': 'ایجاد پیشنهاد',
|
||||
'governance.votingPower': 'قدرت رای',
|
||||
|
||||
// Common
|
||||
'common.loading': 'در حال بارگذاری...',
|
||||
'common.save': 'ذخیره',
|
||||
'common.cancel': 'لغو',
|
||||
'common.confirm': 'تایید',
|
||||
'common.delete': 'حذف',
|
||||
'common.edit': 'ویرایش',
|
||||
'common.search': 'جستجو',
|
||||
'common.filter': 'فیلتر',
|
||||
'common.sort': 'مرتبسازی',
|
||||
'common.submit': 'ارسال',
|
||||
'common.back': 'بازگشت',
|
||||
'common.next': 'بعدی',
|
||||
'common.previous': 'قبلی',
|
||||
'common.yes': 'بله',
|
||||
'common.no': 'خیر'
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
export default {
|
||||
// Navigation
|
||||
'nav.home': 'Destpêk',
|
||||
'nav.dashboard': 'Panela Kontrolê',
|
||||
'nav.governance': 'Rêveberî',
|
||||
'nav.treasury': 'Xezîne',
|
||||
'nav.staking': 'Staking',
|
||||
'nav.forum': 'Forum',
|
||||
'nav.profile': 'Profîl',
|
||||
'nav.admin': 'Rêvebir',
|
||||
|
||||
// Hero Section
|
||||
'hero.title': 'Platforma Rêveberiya Blockchain',
|
||||
'hero.subtitle': 'Rêveberiya demokratîk û şeffaf a bi teknolojiya blockchain',
|
||||
'hero.cta.primary': 'Dest Pê Bike',
|
||||
'hero.cta.secondary': 'Zêdetir Bizane',
|
||||
|
||||
// Auth
|
||||
'auth.login': 'Têkeve',
|
||||
'auth.logout': 'Derkeve',
|
||||
'auth.signup': 'Tomar Bibe',
|
||||
'auth.email': 'E-peyam',
|
||||
'auth.password': 'Şîfre',
|
||||
'auth.confirmPassword': 'Şîfreyê Piştrast Bike',
|
||||
'auth.rememberMe': 'Min bi bîr bîne',
|
||||
'auth.forgotPassword': 'Şîfreya min ji bîr kir?',
|
||||
|
||||
// Wallet
|
||||
'wallet.connect': 'Wallet Girê Bide',
|
||||
'wallet.disconnect': 'Veqetîne',
|
||||
'wallet.balance': 'Balans',
|
||||
'wallet.address': 'Navnîşan',
|
||||
'wallet.network': 'Tor',
|
||||
|
||||
// Governance
|
||||
'governance.proposals': 'Pêşniyar',
|
||||
'governance.activeProposals': 'Pêşniyarên Çalak',
|
||||
'governance.vote': 'Deng Bide',
|
||||
'governance.delegate': 'Temsîlkar',
|
||||
'governance.createProposal': 'Pêşniyar Biafirîne',
|
||||
'governance.votingPower': 'Hêza Dengdanê',
|
||||
|
||||
// Common
|
||||
'common.loading': 'Tê barkirin...',
|
||||
'common.save': 'Tomar Bike',
|
||||
'common.cancel': 'Betal',
|
||||
'common.confirm': 'Piştrast Bike',
|
||||
'common.delete': 'Jê Bibe',
|
||||
'common.edit': 'Biguherîne',
|
||||
'common.search': 'Lêgerîn',
|
||||
'common.filter': 'Parzûn',
|
||||
'common.sort': 'Rêz Bike',
|
||||
'common.submit': 'Bişîne',
|
||||
'common.back': 'Paşve',
|
||||
'common.next': 'Pêşve',
|
||||
'common.previous': 'Berê',
|
||||
'common.yes': 'Erê',
|
||||
'common.no': 'Na'
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
export default {
|
||||
// Navigation
|
||||
'nav.home': 'Ana Sayfa',
|
||||
'nav.dashboard': 'Kontrol Paneli',
|
||||
'nav.governance': 'Yönetişim',
|
||||
'nav.treasury': 'Hazine',
|
||||
'nav.staking': 'Staking',
|
||||
'nav.forum': 'Forum',
|
||||
'nav.profile': 'Profil',
|
||||
'nav.admin': 'Yönetici',
|
||||
|
||||
// Hero Section
|
||||
'hero.title': 'Blockchain Yönetişim Platformu',
|
||||
'hero.subtitle': 'Blockchain teknolojisi ile demokratik ve şeffaf yönetişim',
|
||||
'hero.cta.primary': 'Başla',
|
||||
'hero.cta.secondary': 'Daha Fazla Bilgi',
|
||||
|
||||
// Auth
|
||||
'auth.login': 'Giriş Yap',
|
||||
'auth.logout': 'Çıkış Yap',
|
||||
'auth.signup': 'Kayıt Ol',
|
||||
'auth.email': 'E-posta',
|
||||
'auth.password': 'Şifre',
|
||||
'auth.confirmPassword': 'Şifre Tekrar',
|
||||
'auth.rememberMe': 'Beni hatırla',
|
||||
'auth.forgotPassword': 'Şifremi unuttum?',
|
||||
|
||||
// Wallet
|
||||
'wallet.connect': 'Cüzdan Bağla',
|
||||
'wallet.disconnect': 'Bağlantıyı Kes',
|
||||
'wallet.balance': 'Bakiye',
|
||||
'wallet.address': 'Adres',
|
||||
'wallet.network': 'Ağ',
|
||||
|
||||
// Governance
|
||||
'governance.proposals': 'Öneriler',
|
||||
'governance.activeProposals': 'Aktif Öneriler',
|
||||
'governance.vote': 'Oy Ver',
|
||||
'governance.delegate': 'Temsilci',
|
||||
'governance.createProposal': 'Öneri Oluştur',
|
||||
'governance.votingPower': 'Oy Gücü',
|
||||
|
||||
// Common
|
||||
'common.loading': 'Yükleniyor...',
|
||||
'common.save': 'Kaydet',
|
||||
'common.cancel': 'İptal',
|
||||
'common.confirm': 'Onayla',
|
||||
'common.delete': 'Sil',
|
||||
'common.edit': 'Düzenle',
|
||||
'common.search': 'Ara',
|
||||
'common.filter': 'Filtrele',
|
||||
'common.sort': 'Sırala',
|
||||
'common.submit': 'Gönder',
|
||||
'common.back': 'Geri',
|
||||
'common.next': 'İleri',
|
||||
'common.previous': 'Önceki',
|
||||
'common.yes': 'Evet',
|
||||
'common.no': 'Hayır'
|
||||
}
|
||||
@@ -2,12 +2,16 @@ import React, { useState } from 'react';
|
||||
import { usePolkadot } from '@/contexts/PolkadotContext';
|
||||
import { AccountBalance } from '@/components/AccountBalance';
|
||||
import { TransferModal } from '@/components/TransferModal';
|
||||
import { ReceiveModal } from '@/components/ReceiveModal';
|
||||
import { TransactionHistory } from '@/components/TransactionHistory';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { ArrowUpRight, ArrowDownRight, History } from 'lucide-react';
|
||||
|
||||
const WalletDashboard: React.FC = () => {
|
||||
const { selectedAccount } = usePolkadot();
|
||||
const [isTransferModalOpen, setIsTransferModalOpen] = useState(false);
|
||||
const [isReceiveModalOpen, setIsReceiveModalOpen] = useState(false);
|
||||
const [isHistoryModalOpen, setIsHistoryModalOpen] = useState(false);
|
||||
|
||||
if (!selectedAccount) {
|
||||
return (
|
||||
@@ -45,20 +49,20 @@ const WalletDashboard: React.FC = () => {
|
||||
<ArrowUpRight className="w-6 h-6 mb-2" />
|
||||
<span>Send</span>
|
||||
</Button>
|
||||
|
||||
|
||||
<Button
|
||||
onClick={() => setIsReceiveModalOpen(true)}
|
||||
variant="outline"
|
||||
className="border-gray-700 hover:bg-gray-800 h-24 flex flex-col items-center justify-center"
|
||||
disabled
|
||||
>
|
||||
<ArrowDownRight className="w-6 h-6 mb-2" />
|
||||
<span>Receive</span>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={() => setIsHistoryModalOpen(true)}
|
||||
variant="outline"
|
||||
className="border-gray-700 hover:bg-gray-800 h-24 flex flex-col items-center justify-center"
|
||||
disabled
|
||||
>
|
||||
<History className="w-6 h-6 mb-2" />
|
||||
<span>History</span>
|
||||
@@ -74,6 +78,13 @@ const WalletDashboard: React.FC = () => {
|
||||
<p className="text-gray-600 text-sm mt-1">
|
||||
Your transaction history will appear here
|
||||
</p>
|
||||
<Button
|
||||
onClick={() => setIsHistoryModalOpen(true)}
|
||||
variant="outline"
|
||||
className="mt-4 border-gray-700 hover:bg-gray-800"
|
||||
>
|
||||
View All Transactions
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,8 +95,18 @@ const WalletDashboard: React.FC = () => {
|
||||
isOpen={isTransferModalOpen}
|
||||
onClose={() => setIsTransferModalOpen(false)}
|
||||
/>
|
||||
|
||||
<ReceiveModal
|
||||
isOpen={isReceiveModalOpen}
|
||||
onClose={() => setIsReceiveModalOpen(false)}
|
||||
/>
|
||||
|
||||
<TransactionHistory
|
||||
isOpen={isHistoryModalOpen}
|
||||
onClose={() => setIsHistoryModalOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WalletDashboard;
|
||||
export default WalletDashboard;
|
||||
Reference in New Issue
Block a user