mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-06-13 05:31:01 +00:00
feat: Major UI improvements and DEX integration
✨ Features: - Improved navbar with proper flex layout and spacing - Added Governance and Trading dropdown menus - Integrated TokenSwap with AssetConversion pallet support - Added DEX availability check with fallback UI 🎨 UI Improvements: - Fixed navbar logo positioning (left-aligned) - Menu items flow naturally from left to right - Responsive design improvements - Updated social media links in footer 🔧 Technical: - Real-time balance queries from Assets pallet - Exchange rate calculation from liquidity pools - Slippage protection - Transaction event monitoring 📝 Modified files: - AppLayout.tsx: Navbar restructure - TokenSwap.tsx: DEX integration - WalletModal.tsx: Wallet improvements - Login.tsx: Auth updates
This commit is contained in:
+152
-133
@@ -71,180 +71,180 @@ const AppLayout: React.FC = () => {
|
|||||||
<div className="min-h-screen bg-gray-950 text-white">
|
<div className="min-h-screen bg-gray-950 text-white">
|
||||||
{/* Navigation */}
|
{/* Navigation */}
|
||||||
<nav className="fixed top-0 w-full z-40 bg-gray-950/90 backdrop-blur-md border-b border-gray-800">
|
<nav className="fixed top-0 w-full z-40 bg-gray-950/90 backdrop-blur-md border-b border-gray-800">
|
||||||
<div className="container mx-auto px-8">
|
<div className="w-full px-4">
|
||||||
<div className="flex items-center justify-between h-16">
|
<div className="flex items-center justify-between h-16">
|
||||||
<div className="flex items-center">
|
{/* LEFT: Logo */}
|
||||||
<span className="text-xl font-bold bg-gradient-to-r from-green-500 to-yellow-400 bg-clip-text text-transparent">
|
<div className="flex-shrink-0">
|
||||||
|
<span className="text-lg font-bold bg-gradient-to-r from-green-500 to-yellow-400 bg-clip-text text-transparent whitespace-nowrap">
|
||||||
PezkuwiChain
|
PezkuwiChain
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="hidden md:flex items-center space-x-8">
|
{/* CENTER & RIGHT: Menu + Actions in same row */}
|
||||||
|
<div className="hidden lg:flex items-center space-x-4 flex-1 justify-start ml-8 pr-4">
|
||||||
{user ? (
|
{user ? (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/dashboard')}
|
onClick={() => navigate('/dashboard')}
|
||||||
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1"
|
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1 text-sm"
|
||||||
>
|
>
|
||||||
<LayoutDashboard className="w-4 h-4" />
|
<LayoutDashboard className="w-4 h-4" />
|
||||||
|
Dashboard
|
||||||
{t('nav.dashboard', 'Dashboard')}
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/wallet')}
|
onClick={() => navigate('/wallet')}
|
||||||
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1"
|
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1 text-sm"
|
||||||
>
|
>
|
||||||
<Wallet className="w-4 h-4" />
|
<Wallet className="w-4 h-4" />
|
||||||
{t('nav.wallet', 'Wallet')}
|
Wallet
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{/* Governance Dropdown */}
|
||||||
|
<div className="relative group">
|
||||||
|
<button className="text-gray-300 hover:text-white transition-colors flex items-center gap-1 text-sm">
|
||||||
|
<FileEdit className="w-4 h-4" />
|
||||||
|
Governance
|
||||||
|
<svg className="w-3 h-3 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div className="absolute left-0 mt-2 w-48 bg-gray-900 border border-gray-700 rounded-lg shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowProposalWizard(true)}
|
||||||
|
className="w-full text-left px-4 py-2 text-gray-300 hover:bg-gray-800 hover:text-white flex items-center gap-2 rounded-t-lg"
|
||||||
|
>
|
||||||
|
<FileEdit className="w-4 h-4" />
|
||||||
|
{t('nav.proposals')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowDelegation(true)}
|
||||||
|
className="w-full text-left px-4 py-2 text-gray-300 hover:bg-gray-800 hover:text-white flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<Users2 className="w-4 h-4" />
|
||||||
|
{t('nav.delegation')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowForum(true)}
|
||||||
|
className="w-full text-left px-4 py-2 text-gray-300 hover:bg-gray-800 hover:text-white flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<MessageSquare className="w-4 h-4" />
|
||||||
|
{t('nav.forum')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setShowTreasury(true);
|
||||||
|
setTreasuryTab('overview');
|
||||||
|
}}
|
||||||
|
className="w-full text-left px-4 py-2 text-gray-300 hover:bg-gray-800 hover:text-white flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<PiggyBank className="w-4 h-4" />
|
||||||
|
{t('nav.treasury')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowModeration(true)}
|
||||||
|
className="w-full text-left px-4 py-2 text-gray-300 hover:bg-gray-800 hover:text-white flex items-center gap-2 rounded-b-lg"
|
||||||
|
>
|
||||||
|
<ShieldCheck className="w-4 h-4" />
|
||||||
|
{t('nav.moderation')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Trading Dropdown */}
|
||||||
|
<div className="relative group">
|
||||||
|
<button className="text-gray-300 hover:text-white transition-colors flex items-center gap-1 text-sm">
|
||||||
|
<ArrowRightLeft className="w-4 h-4" />
|
||||||
|
Trading
|
||||||
|
<svg className="w-3 h-3 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div className="absolute left-0 mt-2 w-48 bg-gray-900 border border-gray-700 rounded-lg shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowTokenSwap(true)}
|
||||||
|
className="w-full text-left px-4 py-2 text-gray-300 hover:bg-gray-800 hover:text-white flex items-center gap-2 rounded-t-lg"
|
||||||
|
>
|
||||||
|
<Repeat className="w-4 h-4" />
|
||||||
|
Token Swap
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowP2P(true)}
|
||||||
|
className="w-full text-left px-4 py-2 text-gray-300 hover:bg-gray-800 hover:text-white flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<ArrowRightLeft className="w-4 h-4" />
|
||||||
|
P2P Market
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowStaking(true)}
|
||||||
|
className="w-full text-left px-4 py-2 text-gray-300 hover:bg-gray-800 hover:text-white flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<TrendingUp className="w-4 h-4" />
|
||||||
|
Staking
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowMultiSig(true)}
|
||||||
|
className="w-full text-left px-4 py-2 text-gray-300 hover:bg-gray-800 hover:text-white flex items-center gap-2 rounded-b-lg"
|
||||||
|
>
|
||||||
|
<Lock className="w-4 h-4" />
|
||||||
|
MultiSig
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/profile-settings')}
|
onClick={() => navigate('/profile-settings')}
|
||||||
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1"
|
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1 text-sm"
|
||||||
>
|
>
|
||||||
<Settings className="w-4 h-4" />
|
<Settings className="w-4 h-4" />
|
||||||
{t('nav.settings', 'Settings')}
|
Settings
|
||||||
</button>
|
|
||||||
{isAdmin && (
|
|
||||||
<button
|
|
||||||
onClick={() => navigate('/admin')}
|
|
||||||
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<UserCog className="w-4 h-4" />
|
|
||||||
{t('nav.admin', 'Admin')}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
onClick={() => setShowProposalWizard(true)}
|
|
||||||
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<FileEdit className="w-4 h-4" />
|
|
||||||
{t('nav.proposals')}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setShowDelegation(true)}
|
|
||||||
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<Users2 className="w-4 h-4" />
|
|
||||||
{t('nav.delegation')}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setShowForum(true)}
|
|
||||||
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<MessageSquare className="w-4 h-4" />
|
|
||||||
{t('nav.forum')}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
setShowTreasury(true);
|
|
||||||
setTreasuryTab('overview');
|
|
||||||
}}
|
|
||||||
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<PiggyBank className="w-4 h-4" />
|
|
||||||
{t('nav.treasury')}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setShowModeration(true)}
|
|
||||||
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<ShieldCheck className="w-4 h-4" />
|
|
||||||
{t('nav.moderation')}
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await signOut();
|
await signOut();
|
||||||
navigate('/login');
|
navigate('/login');
|
||||||
}}
|
}}
|
||||||
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1"
|
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1 text-sm"
|
||||||
>
|
>
|
||||||
<LogIn className="w-4 h-4 rotate-180" />
|
<LogIn className="w-4 h-4 rotate-180" />
|
||||||
{t('nav.logout', 'Logout')}
|
Logout
|
||||||
</button>
|
</button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/login')}
|
onClick={() => navigate('/login')}
|
||||||
className="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition-colors flex items-center gap-2"
|
className="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg transition-colors flex items-center gap-2 text-sm"
|
||||||
>
|
>
|
||||||
<LogIn className="w-4 h-4" />
|
<LogIn className="w-4 h-4" />
|
||||||
{t('nav.login', 'Login')}
|
Login
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
|
||||||
onClick={() => document.getElementById('governance')?.scrollIntoView({ behavior: 'smooth' })}
|
|
||||||
className="text-gray-300 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
{t('nav.governance')}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => setShowStaking(true)}
|
|
||||||
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<TrendingUp className="w-4 h-4" />
|
|
||||||
Staking
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => setShowP2P(true)}
|
|
||||||
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<ArrowRightLeft className="w-4 h-4" />
|
|
||||||
P2P
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => setShowTokenSwap(true)}
|
|
||||||
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<Repeat className="w-4 h-4" />
|
|
||||||
Token Swap
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={() => setShowMultiSig(true)}
|
|
||||||
className="text-gray-300 hover:text-white transition-colors flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<Lock className="w-4 h-4" />
|
|
||||||
MultiSig
|
|
||||||
</button>
|
|
||||||
<a
|
<a
|
||||||
href="https://docs.pezkuwichain.io"
|
href="https://docs.pezkuwichain.io"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-gray-300 hover:text-white transition-colors"
|
className="text-gray-300 hover:text-white transition-colors text-sm"
|
||||||
>
|
>
|
||||||
{t('nav.docs')}
|
Docs
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center space-x-4">
|
{/* Divider */}
|
||||||
<div className="flex items-center space-x-2">
|
<div className="w-px h-6 bg-gray-700"></div>
|
||||||
|
|
||||||
|
{/* Actions continue after Docs */}
|
||||||
|
<div className="flex items-center">
|
||||||
{isConnected ? (
|
{isConnected ? (
|
||||||
<div className="flex items-center text-green-500 text-sm">
|
<Wifi className="w-4 h-4 text-green-500" />
|
||||||
<Wifi className="w-4 h-4 mr-1" />
|
|
||||||
<span className="hidden sm:inline">Live</span>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center text-gray-500 text-sm">
|
<WifiOff className="w-4 h-4 text-gray-500" />
|
||||||
<WifiOff className="w-4 h-4 mr-1" />
|
|
||||||
<span className="hidden sm:inline">Offline</span>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NotificationBell />
|
<NotificationBell />
|
||||||
<LanguageSwitcher />
|
<LanguageSwitcher />
|
||||||
<PolkadotWalletButton />
|
<PolkadotWalletButton />
|
||||||
<a
|
|
||||||
href="https://github.com/pezkuwichain"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-gray-400 hover:text-white transition-colors"
|
|
||||||
>
|
|
||||||
<Github className="w-5 h-5" />
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -265,19 +265,19 @@ const AppLayout: React.FC = () => {
|
|||||||
<DelegationManager />
|
<DelegationManager />
|
||||||
) : showForum ? (
|
) : showForum ? (
|
||||||
<div className="pt-20 min-h-screen bg-gray-950">
|
<div className="pt-20 min-h-screen bg-gray-950">
|
||||||
<div className="max-w-full mx-auto px-8">
|
<div className="max-w-full mx-auto px-4">
|
||||||
<ForumOverview />
|
<ForumOverview />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : showModeration ? (
|
) : showModeration ? (
|
||||||
<div className="pt-20 min-h-screen bg-gray-950">
|
<div className="pt-20 min-h-screen bg-gray-950">
|
||||||
<div className="max-w-full mx-auto px-8">
|
<div className="max-w-full mx-auto px-4">
|
||||||
<ModerationPanel />
|
<ModerationPanel />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : showTreasury ? (
|
) : showTreasury ? (
|
||||||
<div className="pt-20 min-h-screen bg-gray-950">
|
<div className="pt-20 min-h-screen bg-gray-950">
|
||||||
<div className="max-w-full mx-auto px-8">
|
<div className="max-w-full mx-auto px-4">
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
<h2 className="text-4xl font-bold mb-4 bg-gradient-to-r from-green-500 via-yellow-400 to-red-500 bg-clip-text text-transparent">
|
<h2 className="text-4xl font-bold mb-4 bg-gradient-to-r from-green-500 via-yellow-400 to-red-500 bg-clip-text text-transparent">
|
||||||
{t('treasury.title', 'Treasury Management')}
|
{t('treasury.title', 'Treasury Management')}
|
||||||
@@ -327,7 +327,7 @@ const AppLayout: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : showStaking ? (
|
) : showStaking ? (
|
||||||
<div className="pt-20 min-h-screen bg-gray-950">
|
<div className="pt-20 min-h-screen bg-gray-950">
|
||||||
<div className="max-w-full mx-auto px-8">
|
<div className="max-w-full mx-auto px-4">
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
<h2 className="text-4xl font-bold mb-4 bg-gradient-to-r from-green-500 via-yellow-400 to-red-500 bg-clip-text text-transparent">
|
<h2 className="text-4xl font-bold mb-4 bg-gradient-to-r from-green-500 via-yellow-400 to-red-500 bg-clip-text text-transparent">
|
||||||
Staking Rewards
|
Staking Rewards
|
||||||
@@ -341,7 +341,7 @@ const AppLayout: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : showP2P ? (
|
) : showP2P ? (
|
||||||
<div className="pt-20 min-h-screen bg-gray-950">
|
<div className="pt-20 min-h-screen bg-gray-950">
|
||||||
<div className="max-w-full mx-auto px-8">
|
<div className="max-w-full mx-auto px-4">
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
<h2 className="text-4xl font-bold mb-4 bg-gradient-to-r from-green-500 via-yellow-400 to-red-500 bg-clip-text text-transparent">
|
<h2 className="text-4xl font-bold mb-4 bg-gradient-to-r from-green-500 via-yellow-400 to-red-500 bg-clip-text text-transparent">
|
||||||
P2P Trading Market
|
P2P Trading Market
|
||||||
@@ -355,7 +355,7 @@ const AppLayout: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : showTokenSwap ? (
|
) : showTokenSwap ? (
|
||||||
<div className="pt-20 min-h-screen bg-gray-950">
|
<div className="pt-20 min-h-screen bg-gray-950">
|
||||||
<div className="max-w-full mx-auto px-8">
|
<div className="max-w-full mx-auto px-4">
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
<h2 className="text-4xl font-bold mb-4 bg-gradient-to-r from-purple-500 via-pink-400 to-yellow-500 bg-clip-text text-transparent">
|
<h2 className="text-4xl font-bold mb-4 bg-gradient-to-r from-purple-500 via-pink-400 to-yellow-500 bg-clip-text text-transparent">
|
||||||
PEZ/HEZ Token Swap
|
PEZ/HEZ Token Swap
|
||||||
@@ -369,7 +369,7 @@ const AppLayout: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : showMultiSig ? (
|
) : showMultiSig ? (
|
||||||
<div className="pt-20 min-h-screen bg-gray-950">
|
<div className="pt-20 min-h-screen bg-gray-950">
|
||||||
<div className="max-w-full mx-auto px-8">
|
<div className="max-w-full mx-auto px-4">
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
<h2 className="text-4xl font-bold mb-4 bg-gradient-to-r from-green-500 via-yellow-400 to-red-500 bg-clip-text text-transparent">
|
<h2 className="text-4xl font-bold mb-4 bg-gradient-to-r from-green-500 via-yellow-400 to-red-500 bg-clip-text text-transparent">
|
||||||
Multi-Signature Wallet
|
Multi-Signature Wallet
|
||||||
@@ -431,7 +431,11 @@ const AppLayout: React.FC = () => {
|
|||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<footer className="bg-gray-950 border-t border-gray-800 py-12">
|
<footer className="bg-gray-950 border-t border-gray-800 py-12">
|
||||||
<div className="max-w-full mx-auto px-8">
|
<div className="mt-4 space-y-1 text-sm text-gray-400">
|
||||||
|
<p>📧 info@pezkuwichain.io</p>
|
||||||
|
<p>📧 info@pezkuwichain.app</p>
|
||||||
|
</div>
|
||||||
|
<div className="max-w-full mx-auto px-4">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-semibold mb-4 bg-gradient-to-r from-green-500 to-yellow-400 bg-clip-text text-transparent">
|
<h3 className="text-lg font-semibold mb-4 bg-gradient-to-r from-green-500 to-yellow-400 bg-clip-text text-transparent">
|
||||||
@@ -479,18 +483,33 @@ const AppLayout: React.FC = () => {
|
|||||||
<h4 className="text-white font-semibold mb-4">{t('footer.community')}</h4>
|
<h4 className="text-white font-semibold mb-4">{t('footer.community')}</h4>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-2">
|
||||||
<li>
|
<li>
|
||||||
<a href="#" className="text-gray-400 hover:text-white text-sm">
|
<a href="https://discord.gg/pezkuwichain" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-white text-sm flex items-center">
|
||||||
Discord
|
Discord
|
||||||
|
<ExternalLink className="w-3 h-3 ml-1" />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" className="text-gray-400 hover:text-white text-sm">
|
<a href="https://x.com/PezkuwiChain" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-white text-sm flex items-center">
|
||||||
Twitter
|
Twitter/X
|
||||||
|
<ExternalLink className="w-3 h-3 ml-1" />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="#" className="text-gray-400 hover:text-white text-sm">
|
<a href="https://t.me/PezkuwiApp" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-white text-sm flex items-center">
|
||||||
Telegram
|
Telegram
|
||||||
|
<ExternalLink className="w-3 h-3 ml-1" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.youtube.com/@SatoshiQazi" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-white text-sm flex items-center">
|
||||||
|
YouTube
|
||||||
|
<ExternalLink className="w-3 h-3 ml-1" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://facebook.com/profile.php?id=61582484611719" target="_blank" rel="noopener noreferrer" className="text-gray-400 hover:text-white text-sm flex items-center">
|
||||||
|
Facebook
|
||||||
|
<ExternalLink className="w-3 h-3 ml-1" />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
+308
-73
@@ -1,15 +1,19 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { ArrowDownUp, Settings, Info, TrendingUp, Clock } from 'lucide-react';
|
import { ArrowDownUp, Settings, TrendingUp, Clock, AlertCircle } from 'lucide-react';
|
||||||
import { Card } from '@/components/ui/card';
|
import { Card } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
import { usePolkadot } from '@/contexts/PolkadotContext';
|
import { usePolkadot } from '@/contexts/PolkadotContext';
|
||||||
import { ASSET_IDS, formatBalance } from '@/lib/wallet';
|
import { ASSET_IDS, formatBalance, parseAmount } from '@/lib/wallet';
|
||||||
import { toast } from '@/components/ui/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
|
|
||||||
const TokenSwap = () => {
|
const TokenSwap = () => {
|
||||||
const { api, isApiReady, selectedAccount } = usePolkadot();
|
const { api, isApiReady, selectedAccount } = usePolkadot();
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
const [fromToken, setFromToken] = useState('PEZ');
|
const [fromToken, setFromToken] = useState('PEZ');
|
||||||
const [toToken, setToToken] = useState('HEZ');
|
const [toToken, setToToken] = useState('HEZ');
|
||||||
const [fromAmount, setFromAmount] = useState('');
|
const [fromAmount, setFromAmount] = useState('');
|
||||||
@@ -18,13 +22,35 @@ const TokenSwap = () => {
|
|||||||
const [showConfirm, setShowConfirm] = useState(false);
|
const [showConfirm, setShowConfirm] = useState(false);
|
||||||
const [isSwapping, setIsSwapping] = useState(false);
|
const [isSwapping, setIsSwapping] = useState(false);
|
||||||
|
|
||||||
|
// DEX availability check
|
||||||
|
const [isDexAvailable, setIsDexAvailable] = useState(false);
|
||||||
|
|
||||||
// Real balances from blockchain
|
// Real balances from blockchain
|
||||||
const [fromBalance, setFromBalance] = useState('0');
|
const [fromBalance, setFromBalance] = useState('0');
|
||||||
const [toBalance, setToBalance] = useState('0');
|
const [toBalance, setToBalance] = useState('0');
|
||||||
const [exchangeRate, setExchangeRate] = useState(2.5); // Will be fetched from pool
|
const [exchangeRate, setExchangeRate] = useState(0);
|
||||||
const [isLoadingBalances, setIsLoadingBalances] = useState(false);
|
const [isLoadingBalances, setIsLoadingBalances] = useState(false);
|
||||||
|
const [isLoadingRate, setIsLoadingRate] = useState(false);
|
||||||
|
|
||||||
const toAmount = fromAmount ? (parseFloat(fromAmount) * exchangeRate).toFixed(4) : '';
|
// Liquidity pool data
|
||||||
|
const [liquidityPools, setLiquidityPools] = useState<any[]>([]);
|
||||||
|
const [isLoadingPools, setIsLoadingPools] = useState(false);
|
||||||
|
|
||||||
|
const toAmount = fromAmount && exchangeRate > 0
|
||||||
|
? (parseFloat(fromAmount) * exchangeRate).toFixed(4)
|
||||||
|
: '';
|
||||||
|
|
||||||
|
// Check if AssetConversion pallet is available
|
||||||
|
useEffect(() => {
|
||||||
|
if (api && isApiReady) {
|
||||||
|
const hasAssetConversion = api.tx.assetConversion !== undefined;
|
||||||
|
setIsDexAvailable(hasAssetConversion);
|
||||||
|
|
||||||
|
if (!hasAssetConversion) {
|
||||||
|
console.warn('AssetConversion pallet not available in runtime');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [api, isApiReady]);
|
||||||
|
|
||||||
// Fetch balances from blockchain
|
// Fetch balances from blockchain
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -63,22 +89,108 @@ const TokenSwap = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchBalances();
|
fetchBalances();
|
||||||
}, [api, isApiReady, selectedAccount, fromToken, toToken]);
|
}, [api, isApiReady, selectedAccount, fromToken, toToken, toast]);
|
||||||
|
|
||||||
// TODO: Fetch exchange rate from DEX pool
|
// Fetch exchange rate from AssetConversion pool
|
||||||
// This should query the liquidity pool to get real-time exchange rates
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Placeholder: In real implementation, query pool reserves
|
const fetchExchangeRate = async () => {
|
||||||
// const fetchExchangeRate = async () => {
|
if (!api || !isApiReady || !isDexAvailable) {
|
||||||
// if (!api || !isApiReady) return;
|
return;
|
||||||
// const pool = await api.query.dex.pools([fromAssetId, toAssetId]);
|
}
|
||||||
// // Calculate rate from pool reserves
|
|
||||||
// };
|
|
||||||
|
|
||||||
// Mock exchange rate for now
|
setIsLoadingRate(true);
|
||||||
const mockRate = fromToken === 'PEZ' ? 2.5 : 0.4;
|
try {
|
||||||
setExchangeRate(mockRate);
|
const fromAssetId = ASSET_IDS[fromToken as keyof typeof ASSET_IDS];
|
||||||
}, [api, isApiReady, fromToken, toToken]);
|
const toAssetId = ASSET_IDS[toToken as keyof typeof ASSET_IDS];
|
||||||
|
|
||||||
|
// Create pool asset tuple [asset1, asset2]
|
||||||
|
const poolAssets = [
|
||||||
|
{ NativeOrAsset: { Asset: fromAssetId } },
|
||||||
|
{ NativeOrAsset: { Asset: toAssetId } }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Query pool from AssetConversion pallet
|
||||||
|
const poolInfo = await api.query.assetConversion.pools(poolAssets);
|
||||||
|
|
||||||
|
if (poolInfo && !poolInfo.isEmpty) {
|
||||||
|
const pool = poolInfo.toJSON() as any;
|
||||||
|
|
||||||
|
if (pool && pool[0] && pool[1]) {
|
||||||
|
// Pool structure: [reserve0, reserve1]
|
||||||
|
const reserve0 = parseFloat(pool[0].toString());
|
||||||
|
const reserve1 = parseFloat(pool[1].toString());
|
||||||
|
|
||||||
|
// Calculate exchange rate
|
||||||
|
const rate = reserve1 / reserve0;
|
||||||
|
setExchangeRate(rate);
|
||||||
|
} else {
|
||||||
|
console.warn('Pool has no reserves');
|
||||||
|
setExchangeRate(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('No liquidity pool found for this pair');
|
||||||
|
setExchangeRate(0);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch exchange rate:', error);
|
||||||
|
setExchangeRate(0);
|
||||||
|
} finally {
|
||||||
|
setIsLoadingRate(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchExchangeRate();
|
||||||
|
}, [api, isApiReady, isDexAvailable, fromToken, toToken]);
|
||||||
|
|
||||||
|
// Fetch liquidity pools
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchLiquidityPools = async () => {
|
||||||
|
if (!api || !isApiReady || !isDexAvailable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoadingPools(true);
|
||||||
|
try {
|
||||||
|
// Query all pools from AssetConversion pallet
|
||||||
|
const poolsEntries = await api.query.assetConversion.pools.entries();
|
||||||
|
|
||||||
|
if (poolsEntries && poolsEntries.length > 0) {
|
||||||
|
const pools = poolsEntries.map(([key, value]: any) => {
|
||||||
|
const poolData = value.toJSON();
|
||||||
|
const poolKey = key.toHuman();
|
||||||
|
|
||||||
|
// Calculate TVL from reserves
|
||||||
|
const tvl = poolData && poolData[0] && poolData[1]
|
||||||
|
? ((parseFloat(poolData[0]) + parseFloat(poolData[1])) / 1e12).toFixed(2)
|
||||||
|
: '0';
|
||||||
|
|
||||||
|
// Parse asset IDs from pool key
|
||||||
|
const assets = poolKey?.[0] || [];
|
||||||
|
const asset1 = assets[0]?.NativeOrAsset?.Asset || '?';
|
||||||
|
const asset2 = assets[1]?.NativeOrAsset?.Asset || '?';
|
||||||
|
|
||||||
|
return {
|
||||||
|
pool: `Asset ${asset1} / Asset ${asset2}`,
|
||||||
|
tvl: `$${tvl}M`,
|
||||||
|
apr: 'TBD', // Requires historical data
|
||||||
|
volume: 'TBD', // Requires event indexing
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
setLiquidityPools(pools.slice(0, 3));
|
||||||
|
} else {
|
||||||
|
setLiquidityPools([]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch liquidity pools:', error);
|
||||||
|
setLiquidityPools([]);
|
||||||
|
} finally {
|
||||||
|
setIsLoadingPools(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchLiquidityPools();
|
||||||
|
}, [api, isApiReady, isDexAvailable]);
|
||||||
|
|
||||||
const handleSwap = () => {
|
const handleSwap = () => {
|
||||||
setFromToken(toToken);
|
setFromToken(toToken);
|
||||||
@@ -96,24 +208,99 @@ const TokenSwap = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isDexAvailable) {
|
||||||
|
toast({
|
||||||
|
title: 'DEX Not Available',
|
||||||
|
description: 'AssetConversion pallet is not enabled in runtime',
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!exchangeRate || exchangeRate === 0) {
|
||||||
|
toast({
|
||||||
|
title: 'Error',
|
||||||
|
description: 'No liquidity pool available for this pair',
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setIsSwapping(true);
|
setIsSwapping(true);
|
||||||
try {
|
try {
|
||||||
// TODO: Implement actual swap transaction
|
const fromAssetId = ASSET_IDS[fromToken as keyof typeof ASSET_IDS];
|
||||||
// const fromAssetId = ASSET_IDS[fromToken];
|
const toAssetId = ASSET_IDS[toToken as keyof typeof ASSET_IDS];
|
||||||
// const toAssetId = ASSET_IDS[toToken];
|
const amountIn = parseAmount(fromAmount, 12);
|
||||||
// const amount = parseAmount(fromAmount, 12);
|
|
||||||
// await api.tx.dex.swap(fromAssetId, toAssetId, amount, minReceive).signAndSend(...);
|
|
||||||
|
|
||||||
// Simulated swap for now
|
// Calculate minimum amount out based on slippage
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
const minAmountOut = parseAmount(
|
||||||
|
(parseFloat(toAmount) * (1 - parseFloat(slippage) / 100)).toString(),
|
||||||
|
12
|
||||||
|
);
|
||||||
|
|
||||||
toast({
|
// Create path for swap
|
||||||
title: 'Success',
|
const path = [
|
||||||
description: `Swapped ${fromAmount} ${fromToken} for ${toAmount} ${toToken}`,
|
{ NativeOrAsset: { Asset: fromAssetId } },
|
||||||
});
|
{ NativeOrAsset: { Asset: toAssetId } }
|
||||||
|
];
|
||||||
|
|
||||||
setShowConfirm(false);
|
// Get signer from extension
|
||||||
setFromAmount('');
|
const { web3FromAddress } = await import('@polkadot/extension-dapp');
|
||||||
|
const injector = await web3FromAddress(selectedAccount.address);
|
||||||
|
|
||||||
|
// Submit swap transaction to AssetConversion pallet
|
||||||
|
const tx = api.tx.assetConversion.swapExactTokensForTokens(
|
||||||
|
path,
|
||||||
|
amountIn.toString(),
|
||||||
|
minAmountOut.toString(),
|
||||||
|
selectedAccount.address,
|
||||||
|
true // keep_alive
|
||||||
|
);
|
||||||
|
|
||||||
|
await tx.signAndSend(
|
||||||
|
selectedAccount.address,
|
||||||
|
{ signer: injector.signer },
|
||||||
|
({ status, events }) => {
|
||||||
|
if (status.isInBlock) {
|
||||||
|
console.log('Swap in block:', status.asInBlock.toHex());
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: 'Transaction Submitted',
|
||||||
|
description: `Swap in block ${status.asInBlock.toHex().slice(0, 10)}...`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.isFinalized) {
|
||||||
|
console.log('Swap finalized:', status.asFinalized.toHex());
|
||||||
|
|
||||||
|
// Check for successful swap event
|
||||||
|
const swapEvent = events.find(({ event }) =>
|
||||||
|
api.events.assetConversion?.SwapExecuted?.is(event)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (swapEvent) {
|
||||||
|
toast({
|
||||||
|
title: 'Success!',
|
||||||
|
description: `Swapped ${fromAmount} ${fromToken} for ${toAmount} ${toToken}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
setShowConfirm(false);
|
||||||
|
setFromAmount('');
|
||||||
|
|
||||||
|
// Refresh balances
|
||||||
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: 'Error',
|
||||||
|
description: 'Swap transaction failed',
|
||||||
|
variant: 'destructive',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSwapping(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Swap failed:', error);
|
console.error('Swap failed:', error);
|
||||||
toast({
|
toast({
|
||||||
@@ -121,22 +308,44 @@ const TokenSwap = () => {
|
|||||||
description: error.message || 'Swap transaction failed',
|
description: error.message || 'Swap transaction failed',
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
} finally {
|
|
||||||
setIsSwapping(false);
|
setIsSwapping(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const liquidityData = [
|
// Show DEX unavailable message
|
||||||
{ pool: 'PEZ/HEZ', tvl: '2.5M', apr: '24.5%', volume: '850K' },
|
if (!isDexAvailable && isApiReady) {
|
||||||
{ pool: 'PEZ/USDT', tvl: '1.8M', apr: '18.2%', volume: '620K' },
|
return (
|
||||||
{ pool: 'HEZ/USDT', tvl: '1.2M', apr: '21.8%', volume: '480K' }
|
<div className="max-w-4xl mx-auto">
|
||||||
];
|
<Card className="p-8">
|
||||||
|
<div className="text-center space-y-6">
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<div className="p-4 bg-yellow-500/10 rounded-full">
|
||||||
|
<AlertCircle className="w-12 h-12 text-yellow-500" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
const txHistory = [
|
<div>
|
||||||
{ from: 'PEZ', to: 'HEZ', amount: '1000', rate: '2.48', time: '2 min ago' },
|
<h2 className="text-2xl font-bold mb-2">DEX Coming Soon</h2>
|
||||||
{ from: 'HEZ', to: 'PEZ', amount: '500', rate: '0.41', time: '5 min ago' },
|
<p className="text-gray-400 max-w-md mx-auto">
|
||||||
{ from: 'PEZ', to: 'HEZ', amount: '2500', rate: '2.51', time: '12 min ago' }
|
The AssetConversion pallet is not yet enabled in the runtime.
|
||||||
];
|
Token swapping functionality will be available after the next runtime upgrade.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Badge variant="outline" className="text-yellow-500 border-yellow-500/30">
|
||||||
|
Scheduled for Next Runtime Upgrade
|
||||||
|
</Badge>
|
||||||
|
|
||||||
|
<div className="pt-4">
|
||||||
|
<Button variant="outline" onClick={() => window.location.href = '/'}>
|
||||||
|
Back to Dashboard
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
@@ -149,6 +358,15 @@ const TokenSwap = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{!selectedAccount && (
|
||||||
|
<Alert className="mb-4 bg-yellow-500/10 border-yellow-500/30">
|
||||||
|
<AlertCircle className="h-4 w-4 text-yellow-500" />
|
||||||
|
<AlertDescription className="text-yellow-300">
|
||||||
|
Please connect your wallet to swap tokens
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="bg-gray-50 rounded-lg p-4 text-gray-900">
|
<div className="bg-gray-50 rounded-lg p-4 text-gray-900">
|
||||||
<div className="flex justify-between mb-2">
|
<div className="flex justify-between mb-2">
|
||||||
@@ -164,6 +382,7 @@ const TokenSwap = () => {
|
|||||||
onChange={(e) => setFromAmount(e.target.value)}
|
onChange={(e) => setFromAmount(e.target.value)}
|
||||||
placeholder="0.0"
|
placeholder="0.0"
|
||||||
className="text-2xl font-bold border-0 bg-transparent"
|
className="text-2xl font-bold border-0 bg-transparent"
|
||||||
|
disabled={!selectedAccount}
|
||||||
/>
|
/>
|
||||||
<Button variant="outline" className="min-w-[100px]">
|
<Button variant="outline" className="min-w-[100px]">
|
||||||
{fromToken === 'PEZ' ? '🟣 PEZ' : '🟡 HEZ'}
|
{fromToken === 'PEZ' ? '🟣 PEZ' : '🟡 HEZ'}
|
||||||
@@ -177,6 +396,7 @@ const TokenSwap = () => {
|
|||||||
size="icon"
|
size="icon"
|
||||||
onClick={handleSwap}
|
onClick={handleSwap}
|
||||||
className="rounded-full bg-white border-2"
|
className="rounded-full bg-white border-2"
|
||||||
|
disabled={!selectedAccount}
|
||||||
>
|
>
|
||||||
<ArrowDownUp className="h-5 w-5" />
|
<ArrowDownUp className="h-5 w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -206,7 +426,15 @@ const TokenSwap = () => {
|
|||||||
<div className="bg-blue-50 rounded-lg p-3 text-gray-900">
|
<div className="bg-blue-50 rounded-lg p-3 text-gray-900">
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-gray-900">Exchange Rate</span>
|
<span className="text-gray-900">Exchange Rate</span>
|
||||||
<span className="font-semibold text-gray-900">1 {fromToken} = {exchangeRate} {toToken}</span>
|
<span className="font-semibold text-gray-900">
|
||||||
|
{isLoadingRate ? (
|
||||||
|
'Loading...'
|
||||||
|
) : exchangeRate > 0 ? (
|
||||||
|
`1 ${fromToken} = ${exchangeRate.toFixed(4)} ${toToken}`
|
||||||
|
) : (
|
||||||
|
'No pool available'
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between text-sm mt-1">
|
<div className="flex justify-between text-sm mt-1">
|
||||||
<span className="text-gray-900">Slippage Tolerance</span>
|
<span className="text-gray-900">Slippage Tolerance</span>
|
||||||
@@ -217,9 +445,9 @@ const TokenSwap = () => {
|
|||||||
<Button
|
<Button
|
||||||
className="w-full h-12 text-lg"
|
className="w-full h-12 text-lg"
|
||||||
onClick={() => setShowConfirm(true)}
|
onClick={() => setShowConfirm(true)}
|
||||||
disabled={!fromAmount || parseFloat(fromAmount) <= 0}
|
disabled={!fromAmount || parseFloat(fromAmount) <= 0 || !selectedAccount || exchangeRate === 0}
|
||||||
>
|
>
|
||||||
Swap Tokens
|
{!selectedAccount ? 'Connect Wallet' : exchangeRate === 0 ? 'No Pool Available' : 'Swap Tokens'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -229,20 +457,29 @@ const TokenSwap = () => {
|
|||||||
<TrendingUp className="h-5 w-5" />
|
<TrendingUp className="h-5 w-5" />
|
||||||
Liquidity Pools
|
Liquidity Pools
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
|
||||||
{liquidityData.map((pool, idx) => (
|
{isLoadingPools ? (
|
||||||
<div key={idx} className="flex justify-between items-center p-3 bg-gray-50 rounded-lg text-gray-900">
|
<div className="text-center text-gray-500 py-8">Loading pools...</div>
|
||||||
<div>
|
) : liquidityPools.length > 0 ? (
|
||||||
<div className="font-semibold text-gray-900">{pool.pool}</div>
|
<div className="space-y-3">
|
||||||
<div className="text-sm text-gray-900">TVL: ${pool.tvl}</div>
|
{liquidityPools.map((pool, idx) => (
|
||||||
|
<div key={idx} className="flex justify-between items-center p-3 bg-gray-50 rounded-lg text-gray-900">
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-gray-900">{pool.pool}</div>
|
||||||
|
<div className="text-sm text-gray-900">TVL: {pool.tvl}</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<div className="text-green-600 font-semibold">{pool.apr} APR</div>
|
||||||
|
<div className="text-sm text-gray-900">Vol: {pool.volume}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
))}
|
||||||
<div className="text-green-600 font-semibold">{pool.apr} APR</div>
|
</div>
|
||||||
<div className="text-sm text-gray-900">Vol: ${pool.volume}</div>
|
) : (
|
||||||
</div>
|
<div className="text-center text-gray-500 py-8">
|
||||||
</div>
|
No liquidity pools available yet
|
||||||
))}
|
</div>
|
||||||
</div>
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -250,21 +487,11 @@ const TokenSwap = () => {
|
|||||||
<Card className="p-6">
|
<Card className="p-6">
|
||||||
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
|
||||||
<Clock className="h-5 w-5" />
|
<Clock className="h-5 w-5" />
|
||||||
Recent Transactions
|
Recent Swaps
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
|
||||||
{txHistory.map((tx, idx) => (
|
<div className="text-center text-gray-500 py-8">
|
||||||
<div key={idx} className="p-3 bg-gray-50 rounded-lg text-gray-900">
|
{selectedAccount ? 'No swap history yet' : 'Connect wallet to view history'}
|
||||||
<div className="flex justify-between items-center mb-1">
|
|
||||||
<span className="font-semibold text-gray-900">{tx.amount} {tx.from}</span>
|
|
||||||
<ArrowDownUp className="h-4 w-4 text-gray-400" />
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-between text-sm text-gray-900">
|
|
||||||
<span>Rate: {tx.rate}</span>
|
|
||||||
<span>{tx.time}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
@@ -311,10 +538,18 @@ const TokenSwap = () => {
|
|||||||
<span className="text-gray-900">You Pay</span>
|
<span className="text-gray-900">You Pay</span>
|
||||||
<span className="font-bold text-gray-900">{fromAmount} {fromToken}</span>
|
<span className="font-bold text-gray-900">{fromAmount} {fromToken}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between mb-2">
|
||||||
<span className="text-gray-900">You Receive</span>
|
<span className="text-gray-900">You Receive</span>
|
||||||
<span className="font-bold text-gray-900">{toAmount} {toToken}</span>
|
<span className="font-bold text-gray-900">{toAmount} {toToken}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex justify-between mt-3 pt-3 border-t text-sm">
|
||||||
|
<span className="text-gray-600">Exchange Rate</span>
|
||||||
|
<span className="text-gray-600">1 {fromToken} = {exchangeRate.toFixed(4)} {toToken}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-gray-600">Slippage</span>
|
||||||
|
<span className="text-gray-600">{slippage}%</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
className="w-full"
|
className="w-full"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Wallet, Chrome, Smartphone, Copy, Check, ExternalLink } from 'lucide-react';
|
import { Wallet, Chrome, ExternalLink, Copy, Check, LogOut } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -8,8 +8,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog';
|
} from '@/components/ui/dialog';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { usePolkadot } from '@/contexts/PolkadotContext';
|
||||||
import { useWallet } from '@/contexts/WalletContext';
|
|
||||||
import { formatAddress } from '@/lib/wallet';
|
import { formatAddress } from '@/lib/wallet';
|
||||||
|
|
||||||
interface WalletModalProps {
|
interface WalletModalProps {
|
||||||
@@ -18,24 +17,37 @@ interface WalletModalProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const WalletModal: React.FC<WalletModalProps> = ({ isOpen, onClose }) => {
|
export const WalletModal: React.FC<WalletModalProps> = ({ isOpen, onClose }) => {
|
||||||
const { connectMetaMask, connectWalletConnect, isConnected, address } = useWallet();
|
const {
|
||||||
|
accounts,
|
||||||
|
selectedAccount,
|
||||||
|
setSelectedAccount,
|
||||||
|
connectWallet,
|
||||||
|
disconnectWallet,
|
||||||
|
error
|
||||||
|
} = usePolkadot();
|
||||||
|
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
const handleCopyAddress = () => {
|
const handleCopyAddress = () => {
|
||||||
if (address) {
|
if (selectedAccount?.address) {
|
||||||
navigator.clipboard.writeText(address);
|
navigator.clipboard.writeText(selectedAccount.address);
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 2000);
|
setTimeout(() => setCopied(false), 2000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMetaMaskConnect = async () => {
|
const handleConnect = async () => {
|
||||||
await connectMetaMask();
|
await connectWallet();
|
||||||
if (isConnected) onClose();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleWalletConnectConnect = async () => {
|
const handleSelectAccount = (account: typeof accounts[0]) => {
|
||||||
await connectWalletConnect();
|
setSelectedAccount(account);
|
||||||
|
onClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDisconnect = () => {
|
||||||
|
disconnectWallet();
|
||||||
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -43,82 +55,181 @@ export const WalletModal: React.FC<WalletModalProps> = ({ isOpen, onClose }) =>
|
|||||||
<DialogContent className="sm:max-w-md">
|
<DialogContent className="sm:max-w-md">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="flex items-center gap-2">
|
<DialogTitle className="flex items-center gap-2">
|
||||||
<Wallet className="h-5 w-5 text-kesk" />
|
<Wallet className="h-5 w-5 text-purple-400" />
|
||||||
Connect Wallet
|
{selectedAccount ? 'Wallet Connected' : 'Connect Wallet'}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Connect your wallet to interact with PezkuwiChain governance
|
{selectedAccount
|
||||||
|
? 'Manage your Polkadot account'
|
||||||
|
: 'Connect your Polkadot.js extension to interact with PezkuwiChain'}
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
{!isConnected ? (
|
{/* No Extension Error */}
|
||||||
<Tabs defaultValue="browser" className="w-full">
|
{error && error.includes('extension') && (
|
||||||
<TabsList className="grid w-full grid-cols-2">
|
|
||||||
<TabsTrigger value="browser">Browser Wallet</TabsTrigger>
|
|
||||||
<TabsTrigger value="mobile">Mobile Wallet</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<TabsContent value="browser" className="space-y-4">
|
|
||||||
<Button
|
|
||||||
onClick={handleMetaMaskConnect}
|
|
||||||
className="w-full justify-start bg-kesk hover:bg-kesk/90"
|
|
||||||
>
|
|
||||||
<Chrome className="mr-2 h-5 w-5" />
|
|
||||||
MetaMask
|
|
||||||
</Button>
|
|
||||||
<div className="text-sm text-muted-foreground">
|
|
||||||
Don't have MetaMask?{' '}
|
|
||||||
<a
|
|
||||||
href="https://metamask.io/download/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-kesk hover:underline"
|
|
||||||
>
|
|
||||||
Download here
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="mobile" className="space-y-4">
|
|
||||||
<Button
|
|
||||||
onClick={handleWalletConnectConnect}
|
|
||||||
className="w-full justify-start bg-zer hover:bg-zer/90"
|
|
||||||
>
|
|
||||||
<Smartphone className="mr-2 h-5 w-5" />
|
|
||||||
WalletConnect
|
|
||||||
</Button>
|
|
||||||
<div className="text-sm text-muted-foreground">
|
|
||||||
Scan QR code with your mobile wallet to connect
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
) : (
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between p-3 border rounded-lg">
|
<div className="p-4 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
|
||||||
<div>
|
<p className="text-sm text-yellow-300">
|
||||||
<div className="text-sm text-muted-foreground">Connected Address</div>
|
Polkadot.js extension not detected. Please install it to continue.
|
||||||
<div className="font-mono font-medium">{formatAddress(address!)}</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
|
||||||
size="icon"
|
<div className="flex gap-3">
|
||||||
variant="ghost"
|
<a
|
||||||
onClick={handleCopyAddress}
|
href="https://polkadot.js.org/extension/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex-1"
|
||||||
>
|
>
|
||||||
{copied ? (
|
<Button className="w-full bg-orange-600 hover:bg-orange-700">
|
||||||
<Check className="h-4 w-4 text-kesk" />
|
<Chrome className="mr-2 h-4 w-4" />
|
||||||
) : (
|
Install Extension
|
||||||
<Copy className="h-4 w-4" />
|
</Button>
|
||||||
)}
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-xs text-gray-400 text-center">
|
||||||
|
After installing, refresh the page and try again
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Connected State */}
|
||||||
|
{selectedAccount && !error && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Account Info */}
|
||||||
|
<div className="bg-gray-800/50 rounded-lg p-4 space-y-3">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400 mb-1">Account Name</div>
|
||||||
|
<div className="font-medium">{selectedAccount.meta.name || 'Unnamed Account'}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400 mb-1">Address</div>
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<code className="text-sm font-mono text-gray-300 truncate">
|
||||||
|
{formatAddress(selectedAccount.address)}
|
||||||
|
</code>
|
||||||
|
<Button
|
||||||
|
size="icon"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={handleCopyAddress}
|
||||||
|
className="shrink-0"
|
||||||
|
>
|
||||||
|
{copied ? (
|
||||||
|
<Check className="h-4 w-4 text-green-400" />
|
||||||
|
) : (
|
||||||
|
<Copy className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="text-xs text-gray-400 mb-1">Source</div>
|
||||||
|
<div className="text-sm text-gray-300">
|
||||||
|
{selectedAccount.meta.source || 'polkadot-js'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Actions */}
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="flex-1"
|
||||||
|
onClick={() => window.open(`https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9944#/explorer`, '_blank')}
|
||||||
|
>
|
||||||
|
<ExternalLink className="mr-2 h-4 w-4" />
|
||||||
|
View on Explorer
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleDisconnect}
|
||||||
|
className="text-red-400 border-red-400/30 hover:bg-red-400/10"
|
||||||
|
>
|
||||||
|
<LogOut className="mr-2 h-4 w-4" />
|
||||||
|
Disconnect
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
|
||||||
variant="outline"
|
{/* Switch Account */}
|
||||||
className="w-full"
|
{accounts.length > 1 && (
|
||||||
onClick={() => window.open('https://explorer.pezkuwichain.app/address/' + address, '_blank')}
|
<div className="space-y-2">
|
||||||
>
|
<div className="text-sm text-gray-400">Switch Account</div>
|
||||||
<ExternalLink className="mr-2 h-4 w-4" />
|
<div className="space-y-2 max-h-48 overflow-y-auto">
|
||||||
View on Explorer
|
{accounts.map((account) => (
|
||||||
</Button>
|
<button
|
||||||
|
key={account.address}
|
||||||
|
onClick={() => handleSelectAccount(account)}
|
||||||
|
className={`w-full p-3 rounded-lg border transition-all text-left ${
|
||||||
|
account.address === selectedAccount.address
|
||||||
|
? 'bg-purple-500/20 border-purple-500/50'
|
||||||
|
: 'bg-gray-800/30 border-gray-700 hover:border-gray-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="font-medium text-sm">
|
||||||
|
{account.meta.name || 'Unnamed'}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-400 font-mono">
|
||||||
|
{formatAddress(account.address)}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Not Connected State */}
|
||||||
|
{!selectedAccount && !error && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{accounts.length > 0 ? (
|
||||||
|
// Has accounts, show selection
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="text-sm text-gray-400">Select an account to connect:</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{accounts.map((account) => (
|
||||||
|
<button
|
||||||
|
key={account.address}
|
||||||
|
onClick={() => handleSelectAccount(account)}
|
||||||
|
className="w-full p-4 rounded-lg border border-gray-700 bg-gray-800/50 hover:border-purple-500/50 hover:bg-gray-800 transition-all text-left"
|
||||||
|
>
|
||||||
|
<div className="font-medium mb-1">
|
||||||
|
{account.meta.name || 'Unnamed Account'}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-400 font-mono">
|
||||||
|
{account.address}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// No accounts, show connect button
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Button
|
||||||
|
onClick={handleConnect}
|
||||||
|
className="w-full bg-gradient-to-r from-purple-600 to-cyan-400 hover:from-purple-700 hover:to-cyan-500"
|
||||||
|
>
|
||||||
|
<Wallet className="mr-2 h-4 w-4" />
|
||||||
|
Connect Polkadot.js
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<div className="text-sm text-gray-400 text-center">
|
||||||
|
Don't have Polkadot.js?{' '}
|
||||||
|
<a
|
||||||
|
href="https://polkadot.js.org/extension/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-purple-400 hover:underline"
|
||||||
|
>
|
||||||
|
Download here
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
+32
-17
@@ -1,31 +1,27 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
|
import { usePolkadot } from '@/contexts/PolkadotContext';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { TwoFactorVerify } from '@/components/auth/TwoFactorVerify';
|
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { Eye, EyeOff, Wallet, Mail, Lock, User, AlertCircle, ArrowLeft, UserPlus } from 'lucide-react';
|
import { Eye, EyeOff, Wallet, Mail, Lock, User, AlertCircle, ArrowLeft, UserPlus } from 'lucide-react';
|
||||||
import { useWallet } from '@/contexts/WalletContext';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { supabase } from '@/lib/supabase';
|
|
||||||
|
|
||||||
const Login: React.FC = () => {
|
const Login: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { connect } = useWallet();
|
const { connectWallet, selectedAccount } = usePolkadot();
|
||||||
const { signIn, signUp } = useAuth();
|
const { signIn, signUp } = useAuth();
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [rememberMe, setRememberMe] = useState(false);
|
const [rememberMe, setRememberMe] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [requires2FA, setRequires2FA] = useState(false);
|
|
||||||
const [tempUserId, setTempUserId] = useState('');
|
|
||||||
|
|
||||||
const [loginData, setLoginData] = useState({
|
const [loginData, setLoginData] = useState({
|
||||||
email: '',
|
email: '',
|
||||||
@@ -49,17 +45,16 @@ const Login: React.FC = () => {
|
|||||||
const { error } = await signIn(loginData.email, loginData.password);
|
const { error } = await signIn(loginData.email, loginData.password);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
// More user-friendly error messages
|
|
||||||
if (error.message?.includes('Invalid login credentials')) {
|
if (error.message?.includes('Invalid login credentials')) {
|
||||||
setError('Email veya şifre hatalı. Doğru bilgiler: info@pezkuwichain.io / Sq230515yBkB@#nm90');
|
setError('Email or password is incorrect. Please try again.');
|
||||||
} else {
|
} else {
|
||||||
setError(error.message || 'Giriş başarısız. Lütfen tekrar deneyin.');
|
setError(error.message || 'Login failed. Please try again.');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
navigate('/');
|
navigate('/');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError('Giriş yapılamadı. Lütfen tekrar deneyin.');
|
setError('Login failed. Please try again.');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -83,7 +78,12 @@ const Login: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { error } = await signUp(signupData.email, signupData.password, signupData.name, signupData.referralCode);
|
const { error } = await signUp(
|
||||||
|
signupData.email,
|
||||||
|
signupData.password,
|
||||||
|
signupData.name,
|
||||||
|
signupData.referralCode
|
||||||
|
);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
setError(error.message);
|
setError(error.message);
|
||||||
@@ -99,11 +99,21 @@ const Login: React.FC = () => {
|
|||||||
|
|
||||||
const handleWalletConnect = async () => {
|
const handleWalletConnect = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
setError('');
|
||||||
try {
|
try {
|
||||||
await connect();
|
await connectWallet();
|
||||||
navigate('/');
|
if (selectedAccount) {
|
||||||
} catch (err) {
|
navigate('/');
|
||||||
setError('Failed to connect wallet');
|
} else {
|
||||||
|
setError('Please select an account from your Polkadot.js extension');
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('Wallet connection failed:', err);
|
||||||
|
if (err.message?.includes('extension')) {
|
||||||
|
setError('Polkadot.js extension not found. Please install it first.');
|
||||||
|
} else {
|
||||||
|
setError('Failed to connect wallet. Please try again.');
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -201,6 +211,7 @@ const Login: React.FC = () => {
|
|||||||
{t('login.forgotPassword', 'Forgot password?')}
|
{t('login.forgotPassword', 'Forgot password?')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<Alert className="bg-red-900/20 border-red-800">
|
<Alert className="bg-red-900/20 border-red-800">
|
||||||
<AlertCircle className="h-4 w-4 text-red-500" />
|
<AlertCircle className="h-4 w-4 text-red-500" />
|
||||||
@@ -208,7 +219,6 @@ const Login: React.FC = () => {
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full bg-gradient-to-r from-green-600 to-green-500 hover:from-green-500 hover:to-green-400"
|
className="w-full bg-gradient-to-r from-green-600 to-green-500 hover:from-green-500 hover:to-green-400"
|
||||||
@@ -320,6 +330,7 @@ const Login: React.FC = () => {
|
|||||||
{t('login.referralDescription', 'If someone referred you, enter their code here')}
|
{t('login.referralDescription', 'If someone referred you, enter their code here')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<Alert className="bg-red-900/20 border-red-800">
|
<Alert className="bg-red-900/20 border-red-800">
|
||||||
<AlertCircle className="h-4 w-4 text-red-500" />
|
<AlertCircle className="h-4 w-4 text-red-500" />
|
||||||
@@ -354,8 +365,12 @@ const Login: React.FC = () => {
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
<Wallet className="mr-2 h-4 w-4" />
|
<Wallet className="mr-2 h-4 w-4" />
|
||||||
{t('login.connectWallet', 'Connect Wallet')}
|
{t('login.connectWallet', 'Connect with Polkadot.js')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<p className="mt-2 text-xs text-center text-gray-500">
|
||||||
|
{t('login.walletHint', 'Connect your Polkadot wallet for instant access')}
|
||||||
|
</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
<CardFooter className="text-center text-sm text-gray-500">
|
<CardFooter className="text-center text-sm text-gray-500">
|
||||||
|
|||||||
Reference in New Issue
Block a user