From 6ad5e151eaff111dc8b82a044592d91acdcd8ffd Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Fri, 6 Feb 2026 11:26:25 +0300 Subject: [PATCH] feat: reorganize wallet dashboard and add blockchain token lookup - Move Recent Activity and NFTs to left column - Move token balances to right column under action buttons - Add Token modal now fetches asset info from blockchain - Shows symbol, name, decimals before adding - Search by asset ID with Enter key support --- web/src/components/AddTokenModal.tsx | 208 ++++++++++++++++++++++----- web/src/pages/WalletDashboard.tsx | 164 +++++++++++---------- 2 files changed, 254 insertions(+), 118 deletions(-) diff --git a/web/src/components/AddTokenModal.tsx b/web/src/components/AddTokenModal.tsx index 940d1170..285e22bf 100644 --- a/web/src/components/AddTokenModal.tsx +++ b/web/src/components/AddTokenModal.tsx @@ -9,7 +9,16 @@ import { import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; -import { AlertCircle } from 'lucide-react'; +import { AlertCircle, Search, CheckCircle, Loader2 } from 'lucide-react'; +import { usePezkuwi } from '@/contexts/PezkuwiContext'; + +interface TokenInfo { + assetId: number; + symbol: string; + name: string; + decimals: number; + exists: boolean; +} interface AddTokenModalProps { isOpen: boolean; @@ -22,95 +31,226 @@ export const AddTokenModal: React.FC = ({ onClose, onAddToken, }) => { + const { assetHubApi, isAssetHubReady } = usePezkuwi(); const [assetId, setAssetId] = useState(''); const [error, setError] = useState(''); - const [isLoading, setIsLoading] = useState(false); + const [isSearching, setIsSearching] = useState(false); + const [isAdding, setIsAdding] = useState(false); + const [tokenInfo, setTokenInfo] = useState(null); - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); + // Helper to decode hex string to UTF-8 + const hexToString = (hex: string): string => { + if (!hex || hex === '0x') return ''; + try { + const hexStr = hex.startsWith('0x') ? hex.slice(2) : hex; + const bytes = new Uint8Array(hexStr.match(/.{1,2}/g)?.map(byte => parseInt(byte, 16)) || []); + return new TextDecoder('utf-8').decode(bytes).replace(/\0/g, ''); + } catch { + return ''; + } + }; + + const handleSearch = async () => { setError(''); + setTokenInfo(null); const id = parseInt(assetId); if (isNaN(id) || id < 0) { - setError('Please enter a valid asset ID (number)'); + setError('Please enter a valid asset ID (positive number)'); return; } - setIsLoading(true); + if (!assetHubApi || !isAssetHubReady) { + setError('Asset Hub connection not ready. Please wait...'); + return; + } + + setIsSearching(true); try { - await onAddToken(id); - setAssetId(''); - setError(''); - } catch { - setError('Failed to add token. Please check the asset ID and try again.'); + // Check if asset exists + const assetInfo = await assetHubApi.query.assets.asset(id); + + if (!assetInfo || assetInfo.isNone) { + setError(`Asset #${id} not found on blockchain`); + setIsSearching(false); + return; + } + + // Get asset metadata + const metadata = await assetHubApi.query.assets.metadata(id); + const metaJson = metadata.toJSON() as { symbol?: string; name?: string; decimals?: number }; + + // Decode hex strings + let symbol = metaJson.symbol || ''; + let name = metaJson.name || ''; + + if (typeof symbol === 'string' && symbol.startsWith('0x')) { + symbol = hexToString(symbol); + } + if (typeof name === 'string' && name.startsWith('0x')) { + name = hexToString(name); + } + + // Fallback if no metadata + if (!symbol) symbol = `Asset #${id}`; + if (!name) name = `Unknown Asset`; + + setTokenInfo({ + assetId: id, + symbol: symbol.trim(), + name: name.trim(), + decimals: metaJson.decimals || 12, + exists: true, + }); + } catch (err) { + console.error('Failed to fetch asset:', err); + setError('Failed to fetch asset from blockchain'); } finally { - setIsLoading(false); + setIsSearching(false); + } + }; + + const handleAdd = async () => { + if (!tokenInfo) return; + + setIsAdding(true); + try { + await onAddToken(tokenInfo.assetId); + handleClose(); + } catch { + setError('Failed to add token'); + } finally { + setIsAdding(false); } }; const handleClose = () => { setAssetId(''); setError(''); + setTokenInfo(null); onClose(); }; + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleSearch(); + } + }; + return ( Add Custom Token - Enter the asset ID of the token you want to track. - Note: Core tokens (HEZ, PEZ) are already displayed separately. + Enter the asset ID to fetch token details from blockchain. -
+
+ {/* Search Input */}
- setAssetId(e.target.value)} - placeholder="e.g., 3" - className="bg-gray-800 border-gray-700 text-white placeholder:text-gray-500" - min="0" - required - /> +
+ { + setAssetId(e.target.value); + setTokenInfo(null); + setError(''); + }} + onKeyPress={handleKeyPress} + placeholder="e.g., 1001" + className="bg-gray-800 border-gray-700 text-white placeholder:text-gray-500 flex-1" + min="0" + /> + +

- Each token on the network has a unique asset ID + Known assets: 1001 (DOT), 1002 (ETH), 1003 (BTC)

+ {/* Token Info Display */} + {tokenInfo && ( +
+
+ + Token Found! +
+
+
+ Symbol: + {tokenInfo.symbol} +
+
+ Name: + {tokenInfo.name} +
+
+ Decimals: + {tokenInfo.decimals} +
+
+ Asset ID: + #{tokenInfo.assetId} +
+
+
+ )} + + {/* Error Display */} {error && (
- +

{error}

)} -
+ {/* Action Buttons */} +
- +
); diff --git a/web/src/pages/WalletDashboard.tsx b/web/src/pages/WalletDashboard.tsx index e3a0478b..7426a568 100644 --- a/web/src/pages/WalletDashboard.tsx +++ b/web/src/pages/WalletDashboard.tsx @@ -245,12 +245,86 @@ const WalletDashboard: React.FC = () => {
- {/* Left Column - Balance */} -
- + {/* Left Column - Recent Activity & NFTs */} +
+ {/* Recent Activity */} +
+
+

Recent Activity

+ +
+ + {isLoadingRecent ? ( +
+ +

Loading...

+
+ ) : recentTransactions.length === 0 ? ( +
+ +

No recent transactions

+
+ ) : ( +
+ {recentTransactions.slice(0, 5).map((tx) => ( +
+
+
+ {isIncoming(tx) ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+
+ {isIncoming(tx) ? 'Received' : 'Sent'} +
+
+ #{tx.blockNumber} +
+
+
+
+
+ {isIncoming(tx) ? '+' : '-'}{formatAmount(tx.amount || '0')} +
+
+
+
+ ))} +
+ )} + + +
+ + {/* NFT Collection */} +
- {/* Right Column - Actions */} + {/* Right Column - Actions & Tokens */}
{/* Quick Actions */}
@@ -281,86 +355,8 @@ const WalletDashboard: React.FC = () => {
- {/* Recent Activity */} -
-
-

Recent Activity

- -
- - {isLoadingRecent ? ( -
- -

Loading transactions...

-
- ) : recentTransactions.length === 0 ? ( -
- -

No recent transactions found

-

- Recent activity from last 10 blocks -

-
- ) : ( -
- {recentTransactions.map((tx) => ( -
-
-
- {isIncoming(tx) ? ( -
- -
- ) : ( -
- -
- )} -
-
- {isIncoming(tx) ? 'Received' : 'Sent'} -
-
- Block #{tx.blockNumber} -
-
-
-
-
- {isIncoming(tx) ? '+' : '-'}{formatAmount(tx.amount || '0')} -
-
- {tx.section}.{tx.method} -
-
-
-
- ))} -
- )} - - -
- - {/* NFT Collection */} - + {/* Token Balances */} +