mirror of
https://github.com/pezkuwichain/pezkuwi-telegram-miniapp.git
synced 2026-06-15 03:21:11 +00:00
feat(i18n): replace all hardcoded strings with translation calls
- Add translation keys for dashboard, send, receive, history, swap, pools, staking, lpStaking, fees, tokens, errors, validation, time, and context sections to types.ts and all 6 language files - Replace hardcoded Kurdish/Turkish strings in all wallet components with useTranslation() hook t() calls - Replace hardcoded strings in non-React files (crypto, utils, error-tracking, wallet-storage, contexts) with standalone translate() - Fix Turkish strings incorrectly used in Kurdish codebase
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { useTelegram } from '@/hooks/useTelegram';
|
||||
import { useTranslation } from '@/i18n';
|
||||
|
||||
type TargetChain = 'asset-hub' | 'people';
|
||||
|
||||
@@ -31,14 +32,14 @@ const TARGET_CHAINS: ChainInfo[] = [
|
||||
{
|
||||
id: 'asset-hub',
|
||||
name: 'Asset Hub',
|
||||
description: 'Ji bo PEZ veguheztin',
|
||||
description: 'fees.forTransfers',
|
||||
teyrchainId: 1000,
|
||||
color: 'blue',
|
||||
},
|
||||
{
|
||||
id: 'people',
|
||||
name: 'People Chain',
|
||||
description: 'Ji bo nasname',
|
||||
description: 'fees.forIdentity',
|
||||
teyrchainId: 1004,
|
||||
color: 'purple',
|
||||
},
|
||||
@@ -52,6 +53,7 @@ interface Props {
|
||||
export function FundFeesModal({ isOpen, onClose }: Props) {
|
||||
const { api, assetHubApi, peopleApi, address, keypair } = useWallet();
|
||||
const { hapticImpact, showAlert } = useTelegram();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [targetChain, setTargetChain] = useState<TargetChain>('asset-hub');
|
||||
const [toRelay, setToRelay] = useState(false); // false = Relay→Teyrchain, true = Teyrchain→Relay
|
||||
@@ -142,7 +144,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
|
||||
|
||||
const handleTeleport = async () => {
|
||||
if (!address || !keypair) {
|
||||
showAlert('Cizdan girêdayî nîne');
|
||||
showAlert(t('fees.walletNotConnected'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,18 +153,18 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
|
||||
const sourceApi: any = toRelay ? (targetChain === 'asset-hub' ? assetHubApi : peopleApi) : api;
|
||||
|
||||
if (!sourceApi) {
|
||||
showAlert('API girêdayî nîne');
|
||||
showAlert(t('fees.apiNotConnected'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!amount || parseFloat(amount) <= 0) {
|
||||
showAlert('Mîqdarek rast binivîse');
|
||||
showAlert(t('fees.enterValidAmount'));
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceBalance = getSourceBalance();
|
||||
if (sourceBalance === '--') {
|
||||
showAlert('Zincîr girêdayî nîne');
|
||||
showAlert(t('fees.chainNotConnected'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -170,7 +172,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
|
||||
const currentBalance = parseFloat(sourceBalance);
|
||||
|
||||
if (sendAmount > currentBalance) {
|
||||
showAlert('Bakiye têrê nake');
|
||||
showAlert(t('fees.insufficientBalance'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -282,7 +284,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
|
||||
const xcmPallet = toRelay ? (sourceApi.tx as any).pezkuwiXcm : sourceApi.tx.xcmPallet;
|
||||
|
||||
if (!xcmPallet?.limitedTeleportAssets) {
|
||||
throw new Error('XCM pallet nehate dîtin');
|
||||
throw new Error(t('fees.xcmPalletNotFound'));
|
||||
}
|
||||
|
||||
const tx = xcmPallet.limitedTeleportAssets(
|
||||
@@ -306,7 +308,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
|
||||
}) => {
|
||||
if (status.isFinalized) {
|
||||
if (dispatchError) {
|
||||
let errorMessage = 'Teleport neserketî';
|
||||
let errorMessage = t('fees.teleportFailed');
|
||||
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = sourceApi.registry.findMetaError(dispatchError.asModule);
|
||||
@@ -337,7 +339,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
|
||||
setTxStatus('error');
|
||||
setIsTransferring(false);
|
||||
hapticImpact('heavy');
|
||||
showAlert(error instanceof Error ? error.message : 'Çewtiyekî çêbû');
|
||||
showAlert(error instanceof Error ? error.message : t('fees.errorOccurred'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -361,8 +363,8 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
|
||||
<Fuel className="w-5 h-5 text-yellow-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">Fee Zêde Bike</h2>
|
||||
<p className="text-xs text-muted-foreground">HEZ teleport</p>
|
||||
<h2 className="text-lg font-semibold">{t('fees.title')}</h2>
|
||||
<p className="text-xs text-muted-foreground">{t('fees.subtitle')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -377,27 +379,29 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
|
||||
{txStatus === 'success' ? (
|
||||
<div className="py-8 text-center">
|
||||
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
|
||||
<h3 className="text-xl font-semibold mb-2">Serketî!</h3>
|
||||
<h3 className="text-xl font-semibold mb-2">{t('fees.success')}</h3>
|
||||
<p className="text-muted-foreground">
|
||||
{amount} HEZ bo {getDestName()} hate şandin
|
||||
{t('fees.sentTo', { amount, chain: getDestName() })}
|
||||
</p>
|
||||
</div>
|
||||
) : txStatus === 'error' ? (
|
||||
<div className="py-8 text-center">
|
||||
<AlertCircle className="w-16 h-16 text-red-500 mx-auto mb-4" />
|
||||
<h3 className="text-xl font-semibold mb-2">Neserketî</h3>
|
||||
<h3 className="text-xl font-semibold mb-2">{t('fees.failed')}</h3>
|
||||
<button
|
||||
onClick={() => setTxStatus('idle')}
|
||||
className="mt-4 px-6 py-2 bg-muted rounded-lg"
|
||||
>
|
||||
Dîsa Biceribîne
|
||||
{t('fees.tryAgain')}
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{/* Target Chain Selection */}
|
||||
<div>
|
||||
<label className="text-sm text-muted-foreground mb-2 block">Zincîra Armanc</label>
|
||||
<label className="text-sm text-muted-foreground mb-2 block">
|
||||
{t('fees.targetChain')}
|
||||
</label>
|
||||
<div className="flex gap-2">
|
||||
{TARGET_CHAINS.map((chain) => (
|
||||
<button
|
||||
@@ -420,7 +424,9 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
|
||||
}`}
|
||||
/>
|
||||
<div className="text-sm font-medium">{chain.name}</div>
|
||||
<div className="text-xs text-muted-foreground">{chain.description}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{t(chain.description as any)}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -499,13 +505,15 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
|
||||
targetChain === 'asset-hub' ? 'text-blue-400' : 'text-purple-400'
|
||||
}`}
|
||||
>
|
||||
{selectedChain.description} kêmî 0.1 HEZ tê pêşniyarkirin.
|
||||
{t('fees.minRecommended', { description: t(selectedChain.description as any) })}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Amount Input */}
|
||||
<div>
|
||||
<label className="text-sm text-muted-foreground mb-2 block">Mîqdar (HEZ)</label>
|
||||
<label className="text-sm text-muted-foreground mb-2 block">
|
||||
{t('fees.amountHez')}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
step="0.0001"
|
||||
@@ -537,7 +545,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
|
||||
{/* Status Messages */}
|
||||
{txStatus === 'signing' && (
|
||||
<div className="p-3 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
|
||||
<p className="text-yellow-400 text-sm">Danûstandinê îmze bikin...</p>
|
||||
<p className="text-yellow-400 text-sm">{t('fees.signing')}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -545,7 +553,7 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
|
||||
<div className="p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
|
||||
<p className="text-blue-400 text-sm flex items-center gap-2">
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
XCM Teleport tê çêkirin...
|
||||
{t('fees.xcmTeleportPending')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -559,12 +567,12 @@ export function FundFeesModal({ isOpen, onClose }: Props) {
|
||||
{isTransferring ? (
|
||||
<>
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
{txStatus === 'signing' ? 'Tê îmzekirin...' : 'Tê çêkirin...'}
|
||||
{txStatus === 'signing' ? t('fees.signingButton') : t('fees.processing')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Fuel className="w-5 h-5" />
|
||||
Bo {getDestName()} Bişîne
|
||||
{t('fees.sendTo', { chain: getDestName() })}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useState, useEffect, useCallback } from 'react';
|
||||
import { X, Lock, Unlock, Users, AlertCircle, Loader2, Shield, TrendingUp } from 'lucide-react';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { useTelegram } from '@/hooks/useTelegram';
|
||||
import { useTranslation } from '@/i18n';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface StakingInfo {
|
||||
@@ -35,6 +36,7 @@ const UNITS = 1_000_000_000_000; // 10^12
|
||||
export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
const { api, keypair, address, balance } = useWallet();
|
||||
const { hapticImpact, hapticNotification, showAlert } = useTelegram();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [stakingInfo, setStakingInfo] = useState<StakingInfo | null>(null);
|
||||
const [validators, setValidators] = useState<ValidatorInfo[]>([]);
|
||||
@@ -55,7 +57,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const stakingPallet = api.query.staking as any;
|
||||
if (!stakingPallet) {
|
||||
setError('Staking palleti bulunamadı');
|
||||
setError(t('staking.palletNotFound'));
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
@@ -114,7 +116,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
setValidators(validatorList);
|
||||
} catch (err) {
|
||||
console.error('Error fetching staking info:', err);
|
||||
setError('Staking bilgileri alınamadı');
|
||||
setError(t('staking.fetchError'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -165,7 +167,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
}) => {
|
||||
if (status.isFinalized) {
|
||||
if (dispatchError) {
|
||||
let errorMsg = 'Bond neserketî';
|
||||
let errorMsg = t('staking.bondFailed');
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
||||
errorMsg = `${decoded.section}.${decoded.name}`;
|
||||
@@ -180,13 +182,13 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
});
|
||||
|
||||
hapticNotification('success');
|
||||
showAlert(`${bondAmount} HEZ stake kirin serketî!`);
|
||||
showAlert(t('staking.bondSuccess', { amount: bondAmount }));
|
||||
setBondAmount('');
|
||||
fetchStakingInfo();
|
||||
setActiveTab('status');
|
||||
} catch (err) {
|
||||
console.error('Bond error:', err);
|
||||
setError(err instanceof Error ? err.message : 'Bond neserketî');
|
||||
setError(err instanceof Error ? err.message : t('staking.bondFailed'));
|
||||
hapticNotification('error');
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
@@ -216,7 +218,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
}) => {
|
||||
if (status.isFinalized) {
|
||||
if (dispatchError) {
|
||||
let errorMsg = 'Nominate neserketî';
|
||||
let errorMsg = t('staking.nominateFailed');
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
||||
errorMsg = `${decoded.section}.${decoded.name}`;
|
||||
@@ -231,12 +233,12 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
});
|
||||
|
||||
hapticNotification('success');
|
||||
showAlert(`${selectedValidators.length} validator nominate kirin serketî!`);
|
||||
showAlert(t('staking.nominateSuccess', { count: selectedValidators.length }));
|
||||
fetchStakingInfo();
|
||||
setActiveTab('status');
|
||||
} catch (err) {
|
||||
console.error('Nominate error:', err);
|
||||
setError(err instanceof Error ? err.message : 'Nominate neserketî');
|
||||
setError(err instanceof Error ? err.message : t('staking.nominateFailed'));
|
||||
hapticNotification('error');
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
@@ -268,7 +270,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
}) => {
|
||||
if (status.isFinalized) {
|
||||
if (dispatchError) {
|
||||
let errorMsg = 'Unbond neserketî';
|
||||
let errorMsg = t('staking.unbondFailed');
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = api.registry.findMetaError(dispatchError.asModule);
|
||||
errorMsg = `${decoded.section}.${decoded.name}`;
|
||||
@@ -283,13 +285,13 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
});
|
||||
|
||||
hapticNotification('success');
|
||||
showAlert(`${unbondAmount} HEZ unbond kirin serketî! (28 roj li bendê)`);
|
||||
showAlert(t('staking.unbondSuccess', { amount: unbondAmount }));
|
||||
setUnbondAmount('');
|
||||
fetchStakingInfo();
|
||||
setActiveTab('status');
|
||||
} catch (err) {
|
||||
console.error('Unbond error:', err);
|
||||
setError(err instanceof Error ? err.message : 'Unbond neserketî');
|
||||
setError(err instanceof Error ? err.message : t('staking.unbondFailed'));
|
||||
hapticNotification('error');
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
@@ -330,10 +332,10 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-1 bg-secondary/50 rounded-lg p-1 mb-4">
|
||||
{[
|
||||
{ id: 'status' as const, label: 'Durum', icon: TrendingUp },
|
||||
{ id: 'bond' as const, label: 'Bond', icon: Lock },
|
||||
{ id: 'nominate' as const, label: 'Nominate', icon: Users },
|
||||
{ id: 'unbond' as const, label: 'Unbond', icon: Unlock },
|
||||
{ id: 'status' as const, label: t('staking.statusTab'), icon: TrendingUp },
|
||||
{ id: 'bond' as const, label: t('staking.bondTab'), icon: Lock },
|
||||
{ id: 'nominate' as const, label: t('staking.nominateTab'), icon: Users },
|
||||
{ id: 'unbond' as const, label: t('staking.unbondTab'), icon: Unlock },
|
||||
].map(({ id, label, icon: Icon }) => (
|
||||
<button
|
||||
key={id}
|
||||
@@ -368,7 +370,9 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
{stakingInfo ? (
|
||||
<>
|
||||
<div className="bg-gradient-to-r from-green-500/20 to-emerald-500/20 border border-green-500/30 rounded-xl p-4">
|
||||
<div className="text-sm text-muted-foreground mb-1">Aktîf Stake</div>
|
||||
<div className="text-sm text-muted-foreground mb-1">
|
||||
{t('staking.activeStake')}
|
||||
</div>
|
||||
<div className="text-2xl font-bold text-green-400">
|
||||
{formatHEZ(stakingInfo.active)} HEZ
|
||||
</div>
|
||||
@@ -376,20 +380,24 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
|
||||
<div className="bg-secondary/50 rounded-xl p-4 space-y-3">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Giştî Bonded</span>
|
||||
<span className="text-muted-foreground">{t('staking.totalBonded')}</span>
|
||||
<span>{formatHEZ(stakingInfo.totalBonded)} HEZ</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Nominations</span>
|
||||
<span className="text-muted-foreground">{t('staking.nominations')}</span>
|
||||
<span>{stakingInfo.nominations.length} validator</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Xelat Armanc</span>
|
||||
<span className="text-muted-foreground">
|
||||
{t('staking.rewardDestination')}
|
||||
</span>
|
||||
<span>{stakingInfo.rewardDestination}</span>
|
||||
</div>
|
||||
{stakingInfo.unlocking.length > 0 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Unbonding</span>
|
||||
<span className="text-muted-foreground">
|
||||
{t('staking.unbondingChunks')}
|
||||
</span>
|
||||
<span className="text-yellow-400">
|
||||
{stakingInfo.unlocking.length} chunk
|
||||
</span>
|
||||
@@ -400,7 +408,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
{stakingInfo.nominations.length > 0 && (
|
||||
<div className="bg-secondary/50 rounded-xl p-4">
|
||||
<div className="text-sm text-muted-foreground mb-2">
|
||||
Nominated Validators
|
||||
{t('staking.nominatedValidators')}
|
||||
</div>
|
||||
<div className="space-y-1 max-h-32 overflow-y-auto">
|
||||
{stakingInfo.nominations.map((addr, i) => (
|
||||
@@ -413,24 +421,21 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
)}
|
||||
|
||||
<div className="p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
|
||||
<p className="text-blue-400 text-xs">
|
||||
💡 HEZ stake kirin Trust Score zêde dike. Herî kêm 1 meh stake bikî ji bo
|
||||
bonusê.
|
||||
</p>
|
||||
<p className="text-blue-400 text-xs">{t('staking.stakingTip')}</p>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<Shield className="w-16 h-16 text-muted-foreground mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium mb-2">Hîn Stake Nekiriye</h3>
|
||||
<h3 className="text-lg font-medium mb-2">{t('staking.notStakedYet')}</h3>
|
||||
<p className="text-muted-foreground text-sm mb-4">
|
||||
HEZ stake bike ji bo Trust Score qezenckirin
|
||||
{t('staking.stakeForTrustScore')}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setActiveTab('bond')}
|
||||
className="px-6 py-2 bg-green-600 text-white rounded-lg text-sm"
|
||||
>
|
||||
Dest Pê Bike
|
||||
{t('staking.startStaking')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -442,19 +447,23 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
<div className="space-y-4">
|
||||
<div className="bg-secondary/50 rounded-xl p-4">
|
||||
<div className="flex justify-between text-sm mb-2">
|
||||
<span className="text-muted-foreground">Bakiyê Te</span>
|
||||
<span className="text-muted-foreground">{t('staking.yourBalance')}</span>
|
||||
<span>{balance || '0'} HEZ</span>
|
||||
</div>
|
||||
{stakingInfo && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Niha Staked</span>
|
||||
<span className="text-muted-foreground">
|
||||
{t('staking.currentlyStaked')}
|
||||
</span>
|
||||
<span className="text-green-400">{formatHEZ(stakingInfo.active)} HEZ</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm text-muted-foreground mb-2 block">Mîqdar (HEZ)</label>
|
||||
<label className="text-sm text-muted-foreground mb-2 block">
|
||||
{t('staking.amountHez')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="number"
|
||||
@@ -473,9 +482,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
</div>
|
||||
|
||||
<div className="p-3 bg-yellow-500/10 border border-yellow-500/30 rounded-lg">
|
||||
<p className="text-yellow-400 text-xs">
|
||||
⚠️ Stake kirinê paşê 28 roj li bendê ye ji bo vekişandinê.
|
||||
</p>
|
||||
<p className="text-yellow-400 text-xs">{t('staking.bondWarning')}</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -488,7 +495,11 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
) : (
|
||||
<Lock className="w-5 h-5" />
|
||||
)}
|
||||
{isProcessing ? 'Tê bond kirin...' : stakingInfo ? 'Zêde Bike' : 'Bond Bike'}
|
||||
{isProcessing
|
||||
? t('staking.bonding')
|
||||
: stakingInfo
|
||||
? t('staking.bondExtra')
|
||||
: t('staking.bondButton')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -499,14 +510,12 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
{!stakingInfo ? (
|
||||
<div className="text-center py-8">
|
||||
<AlertCircle className="w-12 h-12 text-yellow-400 mx-auto mb-4" />
|
||||
<p className="text-muted-foreground">
|
||||
Pêşî HEZ bond bike, paşê nominate bike
|
||||
</p>
|
||||
<p className="text-muted-foreground">{t('staking.bondFirst')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="text-sm text-muted-foreground mb-2">
|
||||
Validator hilbijêre (max 16): {selectedValidators.length}/16
|
||||
{t('staking.selectValidators', { count: selectedValidators.length })}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 max-h-64 overflow-y-auto">
|
||||
@@ -525,7 +534,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
{v.address.slice(0, 16)}...{v.address.slice(-8)}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
Komîsyon: {v.commission.toFixed(2)}%
|
||||
{t('staking.commission')} {v.commission.toFixed(2)}%
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
@@ -541,7 +550,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
) : (
|
||||
<Users className="w-5 h-5" />
|
||||
)}
|
||||
{isProcessing ? 'Tê nominate kirin...' : 'Nominate Bike'}
|
||||
{isProcessing ? t('staking.nominating') : t('staking.nominateButton')}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
@@ -554,13 +563,13 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
{!stakingInfo ? (
|
||||
<div className="text-center py-8">
|
||||
<AlertCircle className="w-12 h-12 text-yellow-400 mx-auto mb-4" />
|
||||
<p className="text-muted-foreground">Tu hîn stake nekiriye</p>
|
||||
<p className="text-muted-foreground">{t('staking.notStakedUnbond')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="bg-secondary/50 rounded-xl p-4">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">Aktîf Stake</span>
|
||||
<span className="text-muted-foreground">{t('staking.activeStake')}</span>
|
||||
<span className="text-green-400">
|
||||
{formatHEZ(stakingInfo.active)} HEZ
|
||||
</span>
|
||||
@@ -569,7 +578,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
|
||||
<div>
|
||||
<label className="text-sm text-muted-foreground mb-2 block">
|
||||
Mîqdar (HEZ)
|
||||
{t('staking.amountHez')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
@@ -589,9 +598,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
</div>
|
||||
|
||||
<div className="p-3 bg-orange-500/10 border border-orange-500/30 rounded-lg">
|
||||
<p className="text-orange-400 text-xs">
|
||||
⚠️ Unbond kirin 28 roj digire. Paşê dikare vekişîne.
|
||||
</p>
|
||||
<p className="text-orange-400 text-xs">{t('staking.unbondWarning')}</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -604,7 +611,7 @@ export function HEZStakingModal({ isOpen, onClose }: HEZStakingModalProps) {
|
||||
) : (
|
||||
<Unlock className="w-5 h-5" />
|
||||
)}
|
||||
{isProcessing ? 'Tê unbond kirin...' : 'Unbond Bike'}
|
||||
{isProcessing ? t('staking.unbondProcessing') : t('staking.unbondButton')}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useState, useEffect } from 'react';
|
||||
import { X, Lock, Unlock, Gift, AlertCircle, Loader2 } from 'lucide-react';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { useTelegram } from '@/hooks/useTelegram';
|
||||
import { useTranslation } from '@/i18n';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface StakingPool {
|
||||
@@ -56,6 +57,7 @@ function formatAssetLocation(assetId: number): object {
|
||||
export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
const { assetHubApi, keypair, address } = useWallet();
|
||||
const { hapticImpact, hapticNotification, showAlert } = useTelegram();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [pools, setPools] = useState<StakingPool[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
@@ -76,7 +78,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
const poolEntries = await (assetHubApi.query.assetRewards as any)?.pools?.entries();
|
||||
|
||||
if (!poolEntries) {
|
||||
setError('Staking palleti amade nîne');
|
||||
setError(t('lpStaking.palletNotReady'));
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
@@ -170,7 +172,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching staking pools:', err);
|
||||
setError('Staking pools bar nekirin');
|
||||
setError(t('lpStaking.poolsNotLoaded'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -212,7 +214,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
}) => {
|
||||
if (status.isFinalized) {
|
||||
if (dispatchError) {
|
||||
let errorMsg = 'Stake neserketî';
|
||||
let errorMsg = t('lpStaking.stakeFailed');
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
|
||||
errorMsg = `${decoded.section}.${decoded.name}`;
|
||||
@@ -227,14 +229,14 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
});
|
||||
|
||||
hapticNotification('success');
|
||||
showAlert('Stake serket!');
|
||||
showAlert(t('lpStaking.stakeSuccess'));
|
||||
setStakeAmount('');
|
||||
|
||||
setTimeout(() => {
|
||||
onClose();
|
||||
}, 1500);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Stake neserketî');
|
||||
setError(err instanceof Error ? err.message : t('lpStaking.stakeFailed'));
|
||||
hapticNotification('error');
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
@@ -266,7 +268,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
}) => {
|
||||
if (status.isFinalized) {
|
||||
if (dispatchError) {
|
||||
let errorMsg = 'Unstake neserketî';
|
||||
let errorMsg = t('lpStaking.unstakeFailed');
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
|
||||
errorMsg = `${decoded.section}.${decoded.name}`;
|
||||
@@ -281,14 +283,14 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
});
|
||||
|
||||
hapticNotification('success');
|
||||
showAlert('Unstake serket!');
|
||||
showAlert(t('lpStaking.unstakeSuccess'));
|
||||
setUnstakeAmount('');
|
||||
|
||||
setTimeout(() => {
|
||||
onClose();
|
||||
}, 1500);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unstake neserketî');
|
||||
setError(err instanceof Error ? err.message : t('lpStaking.unstakeFailed'));
|
||||
hapticNotification('error');
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
@@ -318,7 +320,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
}) => {
|
||||
if (status.isFinalized) {
|
||||
if (dispatchError) {
|
||||
let errorMsg = 'Xelat stendin neserketî';
|
||||
let errorMsg = t('lpStaking.claimFailed');
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
|
||||
errorMsg = `${decoded.section}.${decoded.name}`;
|
||||
@@ -333,13 +335,13 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
});
|
||||
|
||||
hapticNotification('success');
|
||||
showAlert('Xelat hat stendin!');
|
||||
showAlert(t('lpStaking.claimSuccess'));
|
||||
|
||||
setTimeout(() => {
|
||||
onClose();
|
||||
}, 1500);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Xelat stendin neserketî');
|
||||
setError(err instanceof Error ? err.message : t('lpStaking.claimFailed'));
|
||||
hapticNotification('error');
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
@@ -370,13 +372,15 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
) : pools.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<AlertCircle className="w-12 h-12 text-muted-foreground mx-auto mb-4" />
|
||||
<p className="text-muted-foreground">Hêj staking pool tune ne</p>
|
||||
<p className="text-muted-foreground">{t('lpStaking.noPoolsYet')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Pool Selector */}
|
||||
<div className="mb-4">
|
||||
<label className="text-sm text-muted-foreground mb-2 block">Pool Hilbijêre</label>
|
||||
<label className="text-sm text-muted-foreground mb-2 block">
|
||||
{t('lpStaking.selectPool')}
|
||||
</label>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{pools.map((pool) => (
|
||||
<button
|
||||
@@ -403,21 +407,21 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
<div className="bg-secondary/50 rounded-xl p-4 mb-4 border border-border">
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<div className="text-muted-foreground">Giştî Staked</div>
|
||||
<div className="text-muted-foreground">{t('lpStaking.totalStaked')}</div>
|
||||
<div className="font-medium">{formatAmount(currentPool.totalStaked)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground">Te Stake Kiriye</div>
|
||||
<div className="text-muted-foreground">{t('lpStaking.youStaked')}</div>
|
||||
<div className="font-medium text-green-400">
|
||||
{formatAmount(currentPool.userStaked)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground">LP Bakiye</div>
|
||||
<div className="text-muted-foreground">{t('lpStaking.lpBalance')}</div>
|
||||
<div className="font-medium">{formatAmount(currentPool.lpBalance)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground">Xelat</div>
|
||||
<div className="text-muted-foreground">{t('lpStaking.reward')}</div>
|
||||
<div className="font-medium text-yellow-400">
|
||||
{formatAmount(currentPool.pendingRewards)} PEZ
|
||||
</div>
|
||||
@@ -429,9 +433,9 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-1 bg-secondary/50 rounded-lg p-1 mb-4">
|
||||
{[
|
||||
{ id: 'stake' as const, label: 'Stake', icon: Lock },
|
||||
{ id: 'unstake' as const, label: 'Unstake', icon: Unlock },
|
||||
{ id: 'rewards' as const, label: 'Xelat', icon: Gift },
|
||||
{ id: 'stake' as const, label: t('lpStaking.stakeTab'), icon: Lock },
|
||||
{ id: 'unstake' as const, label: t('lpStaking.unstakeTab'), icon: Unlock },
|
||||
{ id: 'rewards' as const, label: t('lpStaking.rewardTab'), icon: Gift },
|
||||
].map(({ id, label, icon: Icon }) => (
|
||||
<button
|
||||
key={id}
|
||||
@@ -464,7 +468,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-sm text-muted-foreground mb-2 block">
|
||||
Mîqdar ({currentPool.stakedAsset})
|
||||
{t('lpStaking.amount', { asset: currentPool.stakedAsset })}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
@@ -482,7 +486,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
Bakiye: {formatAmount(currentPool.lpBalance)}
|
||||
{t('lpStaking.balanceLabel')} {formatAmount(currentPool.lpBalance)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -495,7 +499,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
) : (
|
||||
<Lock className="w-5 h-5" />
|
||||
)}
|
||||
{isProcessing ? 'Tê stake kirin...' : 'Stake Bike'}
|
||||
{isProcessing ? t('lpStaking.staking') : t('lpStaking.stakeButton')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -505,7 +509,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-sm text-muted-foreground mb-2 block">
|
||||
Mîqdar ({currentPool.stakedAsset})
|
||||
{t('lpStaking.amount', { asset: currentPool.stakedAsset })}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
@@ -523,7 +527,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
Staked: {formatAmount(currentPool.userStaked)}
|
||||
{t('lpStaking.stakedLabel')} {formatAmount(currentPool.userStaked)}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -536,7 +540,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
) : (
|
||||
<Unlock className="w-5 h-5" />
|
||||
)}
|
||||
{isProcessing ? 'Tê unstake kirin...' : 'Unstake Bike'}
|
||||
{isProcessing ? t('lpStaking.unstaking') : t('lpStaking.unstakeButton')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -549,7 +553,9 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
<div className="text-2xl font-bold text-yellow-400 mb-1">
|
||||
{formatAmount(currentPool.pendingRewards)} PEZ
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">Xelatên li bendê</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{t('lpStaking.pendingRewards')}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleClaimRewards}
|
||||
@@ -561,7 +567,7 @@ export function LPStakingModal({ isOpen, onClose }: LPStakingModalProps) {
|
||||
) : (
|
||||
<Gift className="w-5 h-5" />
|
||||
)}
|
||||
{isProcessing ? 'Tê stendin...' : 'Xelatan Bistîne'}
|
||||
{isProcessing ? t('lpStaking.claiming') : t('lpStaking.claimButton')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { X, Droplets, Plus, Minus, AlertCircle, Check } from 'lucide-react';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { useTelegram } from '@/hooks/useTelegram';
|
||||
import { KurdistanSun } from '@/components/KurdistanSun';
|
||||
import { useTranslation } from '@/i18n';
|
||||
|
||||
interface PoolsModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -52,6 +53,7 @@ const formatAssetLocation = (id: number) => {
|
||||
export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
const { assetHubApi, keypair } = useWallet();
|
||||
const { hapticImpact, hapticNotification } = useTelegram();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [pools, setPools] = useState<Pool[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -267,7 +269,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch pools:', err);
|
||||
if (!isCancelled) {
|
||||
setError('Bağlantı hatası - tekrar deneyin');
|
||||
setError(t('pools.connectionError'));
|
||||
}
|
||||
} finally {
|
||||
if (!isCancelled) {
|
||||
@@ -331,7 +333,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
({ status, dispatchError }: { status: any; dispatchError: any }) => {
|
||||
if (status.isFinalized) {
|
||||
if (dispatchError) {
|
||||
let errorMsg = 'Zêdekirin neserketî';
|
||||
let errorMsg = t('pools.addFailed');
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
|
||||
errorMsg = `${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`;
|
||||
@@ -349,7 +351,12 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
});
|
||||
|
||||
setSuccessMessage(
|
||||
`${amount0} ${selectedPool.asset0Symbol} + ${amount1} ${selectedPool.asset1Symbol} hate zêdekirin`
|
||||
t('pools.addedLiquidity', {
|
||||
amount0,
|
||||
token0: selectedPool.asset0Symbol,
|
||||
amount1,
|
||||
token1: selectedPool.asset1Symbol,
|
||||
})
|
||||
);
|
||||
setSuccess(true);
|
||||
hapticNotification('success');
|
||||
@@ -363,7 +370,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error('Add liquidity failed:', err);
|
||||
setError(err instanceof Error ? err.message : 'Zêdekirin neserketî');
|
||||
setError(err instanceof Error ? err.message : t('pools.addFailed'));
|
||||
hapticNotification('error');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
@@ -376,7 +383,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
|
||||
const lpAmount = parseFloat(lpAmountToRemove);
|
||||
if (lpAmount <= 0 || lpAmount > (selectedPool.userLpBalance || 0)) {
|
||||
setError('Mîqdara LP ne derbasdar e');
|
||||
setError(t('pools.invalidLpAmount'));
|
||||
hapticNotification('error');
|
||||
return;
|
||||
}
|
||||
@@ -420,7 +427,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
({ status, dispatchError }: { status: any; dispatchError: any }) => {
|
||||
if (status.isFinalized) {
|
||||
if (dispatchError) {
|
||||
let errorMsg = 'Derxistin neserketî';
|
||||
let errorMsg = t('pools.removeFailed');
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
|
||||
errorMsg = `${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`;
|
||||
@@ -437,7 +444,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
).catch(reject);
|
||||
});
|
||||
|
||||
setSuccessMessage(`${lpAmountToRemove} LP token hate vegerandin`);
|
||||
setSuccessMessage(t('pools.removedLiquidity', { amount: lpAmountToRemove }));
|
||||
setSuccess(true);
|
||||
hapticNotification('success');
|
||||
|
||||
@@ -449,7 +456,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error('Remove liquidity failed:', err);
|
||||
setError(err instanceof Error ? err.message : 'Derxistin neserketî');
|
||||
setError(err instanceof Error ? err.message : t('pools.removeFailed'));
|
||||
hapticNotification('error');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
@@ -466,7 +473,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
<div className="w-16 h-16 mx-auto bg-green-500/20 rounded-full flex items-center justify-center">
|
||||
<Check className="w-8 h-8 text-green-500" />
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold">Serketî!</h2>
|
||||
<h2 className="text-xl font-semibold">{t('pools.success')}</h2>
|
||||
<p className="text-muted-foreground">{successMessage}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -489,9 +496,9 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
}}
|
||||
className="text-muted-foreground"
|
||||
>
|
||||
← Paş
|
||||
{t('pools.back')}
|
||||
</button>
|
||||
<h2 className="text-lg font-semibold">Liquidity Zêde Bike</h2>
|
||||
<h2 className="text-lg font-semibold">{t('pools.addLiquidity')}</h2>
|
||||
<div className="w-10" />
|
||||
</div>
|
||||
|
||||
@@ -515,7 +522,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">{selectedPool.asset0Symbol} Mîqdar</span>
|
||||
<span className="text-muted-foreground">
|
||||
Bakiye: {balances[selectedPool.asset0Symbol]}
|
||||
{t('swap.balanceLabel')} {balances[selectedPool.asset0Symbol]}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
@@ -543,10 +550,10 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">
|
||||
{selectedPool.asset1Symbol} Mîqdar (otomatîk)
|
||||
{t('pools.amountAuto', { token: selectedPool.asset1Symbol })}
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
Bakiye: {balances[selectedPool.asset1Symbol]}
|
||||
{t('swap.balanceLabel')} {balances[selectedPool.asset1Symbol]}
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
@@ -570,7 +577,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
{isSubmitting ? (
|
||||
<div className="flex flex-col items-center justify-center py-4 space-y-3">
|
||||
<KurdistanSun size={80} />
|
||||
<p className="text-sm text-muted-foreground animate-pulse">Tê zêdekirin...</p>
|
||||
<p className="text-sm text-muted-foreground animate-pulse">{t('pools.adding')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
@@ -579,7 +586,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
className="w-full py-4 bg-gradient-to-r from-green-600 to-blue-600 text-white font-semibold rounded-xl disabled:opacity-50 flex items-center justify-center gap-2"
|
||||
>
|
||||
<Droplets className="w-5 h-5" />
|
||||
Liquidity Zêde Bike
|
||||
{t('pools.addLiquidity')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -603,9 +610,9 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
}}
|
||||
className="text-muted-foreground"
|
||||
>
|
||||
← Paş
|
||||
{t('pools.back')}
|
||||
</button>
|
||||
<h2 className="text-lg font-semibold">Liquidity Derxe</h2>
|
||||
<h2 className="text-lg font-semibold">{t('pools.removeLiquidity')}</h2>
|
||||
<div className="w-10" />
|
||||
</div>
|
||||
|
||||
@@ -617,7 +624,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-center text-sm text-muted-foreground mt-1">
|
||||
LP Bakiye: {selectedPool.userLpBalance?.toFixed(4) || '0'} LP
|
||||
{t('pools.lpBalance')} {selectedPool.userLpBalance?.toFixed(4) || '0'} LP
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -626,7 +633,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
{/* LP Amount */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-muted-foreground">LP Token Mîqdar</span>
|
||||
<span className="text-muted-foreground">{t('pools.lpTokenAmount')}</span>
|
||||
<span className="text-muted-foreground">
|
||||
Max: {selectedPool.userLpBalance?.toFixed(4) || '0'}
|
||||
</span>
|
||||
@@ -651,7 +658,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
{/* Estimated Returns */}
|
||||
{lpAmountToRemove && parseFloat(lpAmountToRemove) > 0 && (
|
||||
<div className="bg-muted/50 rounded-xl p-3 space-y-2 text-sm">
|
||||
<p className="text-muted-foreground">Texmînî vegerandin:</p>
|
||||
<p className="text-muted-foreground">{t('pools.estimatedReturn')}</p>
|
||||
<div className="flex justify-between">
|
||||
<span>{selectedPool.asset0Symbol}</span>
|
||||
<span className="font-mono">
|
||||
@@ -691,7 +698,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
{isSubmitting ? (
|
||||
<div className="flex flex-col items-center justify-center py-4 space-y-3">
|
||||
<KurdistanSun size={80} />
|
||||
<p className="text-sm text-muted-foreground animate-pulse">Tê derxistin...</p>
|
||||
<p className="text-sm text-muted-foreground animate-pulse">{t('pools.removing')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
@@ -700,7 +707,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
className="w-full py-4 bg-gradient-to-r from-red-600 to-orange-600 text-white font-semibold rounded-xl disabled:opacity-50 flex items-center justify-center gap-2"
|
||||
>
|
||||
<Minus className="w-5 h-5" />
|
||||
Liquidity Derxe
|
||||
{t('pools.removeLiquidity')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -715,7 +722,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
<div className="w-full max-w-md bg-card rounded-2xl shadow-xl border border-border overflow-hidden max-h-[90vh] overflow-y-auto">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-border">
|
||||
<h2 className="text-lg font-semibold">Liquidity Pools</h2>
|
||||
<h2 className="text-lg font-semibold">{t('pools.title')}</h2>
|
||||
<button onClick={onClose} className="p-2 rounded-full hover:bg-muted">
|
||||
<X className="w-5 h-5 text-muted-foreground" />
|
||||
</button>
|
||||
@@ -726,12 +733,12 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
{isLoading ? (
|
||||
<div className="flex flex-col items-center justify-center py-8">
|
||||
<KurdistanSun size={80} />
|
||||
<p className="text-muted-foreground mt-3 animate-pulse">Tê barkirin...</p>
|
||||
<p className="text-muted-foreground mt-3 animate-pulse">{t('pools.loadingPools')}</p>
|
||||
</div>
|
||||
) : pools.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<Droplets className="w-12 h-12 mx-auto text-muted-foreground mb-2" />
|
||||
<p className="text-muted-foreground">Pool tune</p>
|
||||
<p className="text-muted-foreground">{t('pools.noPools')}</p>
|
||||
</div>
|
||||
) : (
|
||||
pools.map((pool) => (
|
||||
@@ -760,13 +767,17 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
{/* Pool Stats */}
|
||||
<div className="grid grid-cols-2 gap-2 text-sm mb-3">
|
||||
<div>
|
||||
<span className="text-muted-foreground">Rezerv {pool.asset0Symbol}</span>
|
||||
<span className="text-muted-foreground">
|
||||
{t('pools.reserve')} {pool.asset0Symbol}
|
||||
</span>
|
||||
<p className="font-mono">
|
||||
{pool.reserve0.toLocaleString('en-US', { maximumFractionDigits: 0 })}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">Rezerv {pool.asset1Symbol}</span>
|
||||
<span className="text-muted-foreground">
|
||||
{t('pools.reserve')} {pool.asset1Symbol}
|
||||
</span>
|
||||
<p className="font-mono">
|
||||
{pool.reserve1.toLocaleString('en-US', { maximumFractionDigits: 0 })}
|
||||
</p>
|
||||
@@ -777,7 +788,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
{pool.userLpBalance && pool.userLpBalance > 0 && (
|
||||
<div className="bg-green-500/10 border border-green-500/30 rounded-lg p-2 mb-3 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-green-400">Pozîsyona Te</span>
|
||||
<span className="text-green-400">{t('pools.yourPosition')}</span>
|
||||
<span className="text-green-400 font-mono">
|
||||
{pool.userShare?.toFixed(2)}%
|
||||
</span>
|
||||
@@ -799,7 +810,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
className="flex-1 py-2 bg-gradient-to-r from-green-600/20 to-blue-600/20 border border-green-500/30 text-green-400 font-medium rounded-lg flex items-center justify-center gap-1 text-sm"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Zêde Bike
|
||||
{t('pools.addButton')}
|
||||
</button>
|
||||
{pool.userLpBalance && pool.userLpBalance > 0 && (
|
||||
<button
|
||||
@@ -811,7 +822,7 @@ export function PoolsModal({ isOpen, onClose }: PoolsModalProps) {
|
||||
className="flex-1 py-2 bg-gradient-to-r from-red-600/20 to-orange-600/20 border border-red-500/30 text-red-400 font-medium rounded-lg flex items-center justify-center gap-1 text-sm"
|
||||
>
|
||||
<Minus className="w-4 h-4" />
|
||||
Derxe
|
||||
{t('pools.removeButton')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { X, ArrowDownUp, RefreshCw, AlertCircle, Check } from 'lucide-react';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { useTelegram } from '@/hooks/useTelegram';
|
||||
import { KurdistanSun } from '@/components/KurdistanSun';
|
||||
import { useTranslation } from '@/i18n';
|
||||
|
||||
interface SwapModalProps {
|
||||
isOpen: boolean;
|
||||
@@ -36,6 +37,7 @@ const formatAssetLocation = (id: number) => {
|
||||
export function SwapModal({ isOpen, onClose }: SwapModalProps) {
|
||||
const { assetHubApi, keypair } = useWallet();
|
||||
const { hapticImpact, hapticNotification } = useTelegram();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [fromToken, setFromToken] = useState(TOKENS[0]); // HEZ
|
||||
const [toToken, setToToken] = useState(TOKENS[1]); // PEZ
|
||||
@@ -218,7 +220,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
|
||||
const swapAmount = parseFloat(fromAmount);
|
||||
|
||||
if (swapAmount > fromBalance) {
|
||||
setError('Bakiye têrê nake');
|
||||
setError(t('swap.insufficientBalance'));
|
||||
hapticNotification('error');
|
||||
return;
|
||||
}
|
||||
@@ -262,7 +264,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
|
||||
({ status, dispatchError }: { status: any; dispatchError: any }) => {
|
||||
if (status.isFinalized) {
|
||||
if (dispatchError) {
|
||||
let errorMsg = 'Swap neserketî';
|
||||
let errorMsg = t('swap.swapFailed');
|
||||
if (dispatchError.isModule) {
|
||||
const decoded = assetHubApi.registry.findMetaError(dispatchError.asModule);
|
||||
errorMsg = `${decoded.section}.${decoded.name}: ${decoded.docs.join(' ')}`;
|
||||
@@ -291,7 +293,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error('Swap failed:', err);
|
||||
setError(err instanceof Error ? err.message : 'Swap neserketî');
|
||||
setError(err instanceof Error ? err.message : t('swap.swapFailed'));
|
||||
hapticNotification('error');
|
||||
} finally {
|
||||
setIsSwapping(false);
|
||||
@@ -307,7 +309,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
|
||||
<div className="w-16 h-16 mx-auto bg-green-500/20 rounded-full flex items-center justify-center">
|
||||
<Check className="w-8 h-8 text-green-500" />
|
||||
</div>
|
||||
<h2 className="text-xl font-semibold">Swap Serketî!</h2>
|
||||
<h2 className="text-xl font-semibold">{t('swap.swapSuccess')}</h2>
|
||||
<p className="text-muted-foreground">
|
||||
{fromAmount} {fromToken.symbol} → {toAmount} {toToken.symbol}
|
||||
</p>
|
||||
@@ -321,7 +323,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
|
||||
<div className="w-full max-w-md bg-card rounded-2xl shadow-xl border border-border overflow-hidden max-h-[90vh] overflow-y-auto">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-4 border-b border-border">
|
||||
<h2 className="text-lg font-semibold">Token Swap</h2>
|
||||
<h2 className="text-lg font-semibold">{t('swap.title')}</h2>
|
||||
<button onClick={onClose} className="p-2 rounded-full hover:bg-muted">
|
||||
<X className="w-5 h-5 text-muted-foreground" />
|
||||
</button>
|
||||
@@ -332,7 +334,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
|
||||
{/* From Token */}
|
||||
<div className="bg-muted/50 rounded-xl p-4 space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">Ji (From)</span>
|
||||
<span className="text-sm text-muted-foreground">{t('swap.fromLabel')}</span>
|
||||
<select
|
||||
value={fromToken.symbol}
|
||||
onChange={(e) => {
|
||||
@@ -368,7 +370,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
|
||||
Max
|
||||
</button>
|
||||
<span className="text-muted-foreground">
|
||||
Bakiye: {balances[fromToken.symbol]} {fromToken.symbol}
|
||||
{t('swap.balanceLabel')} {balances[fromToken.symbol]} {fromToken.symbol}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -386,7 +388,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
|
||||
{/* To Token */}
|
||||
<div className="bg-muted/50 rounded-xl p-4 space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">Bo (To)</span>
|
||||
<span className="text-sm text-muted-foreground">{t('swap.toLabel')}</span>
|
||||
<select
|
||||
value={toToken.symbol}
|
||||
onChange={(e) => {
|
||||
@@ -416,7 +418,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
|
||||
/>
|
||||
<div className="flex justify-end text-sm">
|
||||
<span className="text-muted-foreground">
|
||||
Bakiye: {balances[toToken.symbol]} {toToken.symbol}
|
||||
{t('swap.balanceLabel')} {balances[toToken.symbol]} {toToken.symbol}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -424,14 +426,14 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
|
||||
{/* Exchange Rate */}
|
||||
<div className="bg-muted/30 rounded-xl p-3 space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-muted-foreground">Rêjeya Guherandinê</span>
|
||||
<span className="text-muted-foreground">{t('swap.exchangeRate')}</span>
|
||||
<span className="flex items-center gap-2">
|
||||
{isLoadingRate ? (
|
||||
<RefreshCw className="w-4 h-4 animate-spin" />
|
||||
) : exchangeRate ? (
|
||||
`1 ${fromToken.symbol} = ${exchangeRate.toFixed(4)} ${toToken.symbol}`
|
||||
) : (
|
||||
<span className="text-yellow-500">Pool tune</span>
|
||||
<span className="text-yellow-500">{t('swap.noPool')}</span>
|
||||
)}
|
||||
<button onClick={fetchExchangeRate} className="p-1 hover:bg-muted rounded">
|
||||
<RefreshCw className="w-3 h-3" />
|
||||
@@ -457,7 +459,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
|
||||
{isSwapping ? (
|
||||
<div className="flex flex-col items-center justify-center py-4 space-y-3">
|
||||
<KurdistanSun size={80} />
|
||||
<p className="text-sm text-muted-foreground animate-pulse">Tê guhertin...</p>
|
||||
<p className="text-sm text-muted-foreground animate-pulse">{t('swap.swapping')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
@@ -465,7 +467,7 @@ export function SwapModal({ isOpen, onClose }: SwapModalProps) {
|
||||
disabled={!fromAmount || !exchangeRate || parseFloat(fromAmount) <= 0}
|
||||
className="w-full py-4 bg-gradient-to-r from-blue-600 to-purple-600 text-white font-semibold rounded-xl disabled:opacity-50 flex items-center justify-center gap-2"
|
||||
>
|
||||
{!exchangeRate ? 'Pool Tune' : 'Swap Bike'}
|
||||
{!exchangeRate ? t('swap.noPoolButton') : t('swap.swapButton')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { useTelegram } from '@/hooks/useTelegram';
|
||||
import { useTranslation } from '@/i18n';
|
||||
import {
|
||||
subscribeToConnection,
|
||||
getLastError,
|
||||
@@ -189,6 +190,7 @@ interface Props {
|
||||
export function TokensCard({ onSendToken }: Props) {
|
||||
const { address, balance: hezBalance } = useWallet();
|
||||
const { hapticImpact } = useTelegram();
|
||||
const { t } = useTranslation();
|
||||
const [rpcConnected, setRpcConnected] = useState(false);
|
||||
const [endpointName, setEndpointName] = useState<string | null>(null);
|
||||
|
||||
@@ -615,7 +617,7 @@ export function TokensCard({ onSendToken }: Props) {
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Token bigere..."
|
||||
placeholder={t('tokens.searchPlaceholder')}
|
||||
className="w-full pl-9 pr-4 py-2 bg-background rounded-lg text-sm"
|
||||
/>
|
||||
</div>
|
||||
@@ -629,7 +631,7 @@ export function TokensCard({ onSendToken }: Props) {
|
||||
className="w-full py-2 border border-dashed border-border rounded-lg text-sm text-muted-foreground hover:text-white hover:border-cyan-500/50 flex items-center justify-center gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Token Zêde Bike
|
||||
{t('tokens.addToken')}
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -639,7 +641,7 @@ export function TokensCard({ onSendToken }: Props) {
|
||||
type="number"
|
||||
value={newAssetId}
|
||||
onChange={(e) => setNewAssetId(e.target.value)}
|
||||
placeholder="Asset ID binivîse (mînak: 3)"
|
||||
placeholder={t('tokens.assetIdPlaceholder')}
|
||||
className="w-full px-3 py-2 bg-muted rounded-lg text-sm"
|
||||
min="0"
|
||||
/>
|
||||
@@ -648,14 +650,14 @@ export function TokensCard({ onSendToken }: Props) {
|
||||
onClick={() => setShowAddToken(false)}
|
||||
className="flex-1 py-2 bg-muted rounded-lg text-sm"
|
||||
>
|
||||
Betal
|
||||
{t('tokens.cancel')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleAddToken}
|
||||
disabled={!newAssetId}
|
||||
className="flex-1 py-2 bg-cyan-600 rounded-lg text-sm disabled:opacity-50"
|
||||
>
|
||||
Zêde Bike
|
||||
{t('tokens.add')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -667,7 +669,9 @@ export function TokensCard({ onSendToken }: Props) {
|
||||
<div className="mx-4 mb-2 px-3 py-2 bg-green-500/10 border border-green-500/30 rounded-lg flex items-center gap-2">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
|
||||
<div>
|
||||
<p className="text-xs text-green-400 font-medium">Pezkuwichain Girêdayî</p>
|
||||
<p className="text-xs text-green-400 font-medium">
|
||||
{t('tokens.blockchainConnected')}
|
||||
</p>
|
||||
{endpointName && <p className="text-[10px] text-green-400/70">{endpointName}</p>}
|
||||
</div>
|
||||
</div>
|
||||
@@ -675,9 +679,11 @@ export function TokensCard({ onSendToken }: Props) {
|
||||
<div className="mx-4 mb-2 px-3 py-2 bg-yellow-500/10 border border-yellow-500/30 rounded-lg flex items-center gap-2">
|
||||
<RefreshCw className="w-4 h-4 text-yellow-400 animate-spin flex-shrink-0" />
|
||||
<div>
|
||||
<p className="text-xs text-yellow-400 font-medium">Girêdana Blockchain...</p>
|
||||
<p className="text-xs text-yellow-400 font-medium">
|
||||
{t('tokens.connectingBlockchain')}
|
||||
</p>
|
||||
<p className="text-[10px] text-yellow-400/70">
|
||||
{getLastError() || 'RPC serverê tê girêdan...'}
|
||||
{getLastError() || t('tokens.connectingRpc')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -688,7 +694,7 @@ export function TokensCard({ onSendToken }: Props) {
|
||||
{filteredTokens.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<Coins className="w-6 h-6 text-muted-foreground mx-auto mb-2" />
|
||||
<p className="text-sm text-muted-foreground">Token nehat dîtin</p>
|
||||
<p className="text-sm text-muted-foreground">{t('tokens.tokenNotFound')}</p>
|
||||
</div>
|
||||
) : (
|
||||
filteredTokens.map((token) =>
|
||||
@@ -793,7 +799,7 @@ export function TokensCard({ onSendToken }: Props) {
|
||||
{/* Total Value */}
|
||||
{token.valueUsd !== undefined && token.balance !== '--' && (
|
||||
<div className="mt-2 pt-2 border-t border-white/10 flex justify-between items-center">
|
||||
<span className="text-xs text-muted-foreground">Toplam</span>
|
||||
<span className="text-xs text-muted-foreground">{t('tokens.total')}</span>
|
||||
<span className="text-sm font-semibold">
|
||||
≈ $
|
||||
{(
|
||||
@@ -879,7 +885,9 @@ export function TokensCard({ onSendToken }: Props) {
|
||||
{token.balance}
|
||||
</p>
|
||||
{token.balance === '--' ? (
|
||||
<p className="text-xs text-muted-foreground">Tê barkirin...</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('tokens.loadingBalance')}
|
||||
</p>
|
||||
) : token.valueUsd !== undefined ? (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
≈ ${token.valueUsd.toFixed(2)}
|
||||
|
||||
@@ -29,6 +29,7 @@ import { HEZStakingModal } from './HEZStakingModal';
|
||||
import { DepositUSDTModal } from './DepositUSDTModal';
|
||||
import { useWallet } from '@/contexts/WalletContext';
|
||||
import { useTelegram } from '@/hooks/useTelegram';
|
||||
import { useTranslation } from '@/i18n';
|
||||
import { formatAddress } from '@/lib/wallet-service';
|
||||
|
||||
interface Props {
|
||||
@@ -51,6 +52,7 @@ interface Transaction {
|
||||
export function WalletDashboard({ onDisconnect }: Props) {
|
||||
const { address, balance, api, assetHubApi, disconnect, isLoading } = useWallet();
|
||||
const { hapticImpact, hapticNotification, showAlert } = useTelegram();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<'main' | 'send' | 'receive' | 'history'>('main');
|
||||
@@ -583,7 +585,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-xs text-green-400">Girêdayî</p>
|
||||
<p className="text-xs text-green-400">{t('dashboard.connected')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -644,7 +646,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
|
||||
<div className="w-8 h-8 bg-green-500/20 rounded-full flex items-center justify-center">
|
||||
<Send className="w-4 h-4 text-green-400" />
|
||||
</div>
|
||||
<span className="text-xs font-medium">Bişîne</span>
|
||||
<span className="text-xs font-medium">{t('dashboard.send')}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -657,7 +659,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
|
||||
<div className="w-8 h-8 bg-cyan-500/20 rounded-full flex items-center justify-center">
|
||||
<QrCode className="w-4 h-4 text-cyan-400" />
|
||||
</div>
|
||||
<span className="text-xs font-medium">Werbigire</span>
|
||||
<span className="text-xs font-medium">{t('dashboard.receive')}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -702,7 +704,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
|
||||
<button
|
||||
onClick={() => {
|
||||
hapticImpact('light');
|
||||
showAlert('Presale tê de ye! Zûtirîn demekê de dê bête vekirin.');
|
||||
showAlert(t('dashboard.presaleMessage'));
|
||||
}}
|
||||
className="flex flex-col items-center gap-1 p-3 bg-gradient-to-r from-pink-600/20 to-red-500/20 border border-pink-500/30 rounded-xl"
|
||||
>
|
||||
@@ -726,10 +728,8 @@ export function WalletDashboard({ onDisconnect }: Props) {
|
||||
<img src="/tokens/USDT.png" alt="USDT" className="w-8 h-8 rounded-full" />
|
||||
</div>
|
||||
<div className="flex-1 text-left">
|
||||
<div className="font-semibold text-emerald-400">USDT Zêde Bike</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
TON, Polkadot an TRC20 ji zincîrên din
|
||||
</div>
|
||||
<div className="font-semibold text-emerald-400">{t('dashboard.depositUsdt')}</div>
|
||||
<div className="text-xs text-muted-foreground">{t('dashboard.depositUsdtDesc')}</div>
|
||||
</div>
|
||||
<ArrowDownLeft className="w-5 h-5 text-emerald-400" />
|
||||
</button>
|
||||
@@ -739,7 +739,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
|
||||
<div className="px-4 pb-4">
|
||||
<div className="bg-muted/50 border border-border rounded-xl p-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="font-semibold">Çalakiya Dawî</h3>
|
||||
<h3 className="font-semibold">{t('dashboard.recentActivity')}</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -747,7 +747,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
|
||||
setActiveTab('history');
|
||||
}}
|
||||
className="text-gray-400 hover:text-white p-1"
|
||||
title="Dîrok"
|
||||
title={t('dashboard.history')}
|
||||
>
|
||||
<History className="w-4 h-4" />
|
||||
</button>
|
||||
@@ -755,7 +755,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
|
||||
onClick={handleRefresh}
|
||||
disabled={isLoadingTxs}
|
||||
className="text-gray-400 hover:text-white p-1"
|
||||
title="Nûkirin"
|
||||
title={t('dashboard.refreshTx')}
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${isLoadingTxs ? 'animate-spin' : ''}`} />
|
||||
</button>
|
||||
@@ -765,13 +765,13 @@ export function WalletDashboard({ onDisconnect }: Props) {
|
||||
{isLoadingTxs ? (
|
||||
<div className="text-center py-8">
|
||||
<RefreshCw className="w-8 h-8 text-gray-600 mx-auto mb-2 animate-spin" />
|
||||
<p className="text-gray-400 text-sm">Tê barkirin...</p>
|
||||
<p className="text-gray-400 text-sm">{t('dashboard.loadingTx')}</p>
|
||||
</div>
|
||||
) : recentTxs.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<History className="w-8 h-8 text-gray-600 mx-auto mb-2" />
|
||||
<p className="text-gray-500 text-sm">Transaksiyona dawî tune</p>
|
||||
<p className="text-gray-600 text-xs mt-1">Dîroka te dê li vir xuya bibe</p>
|
||||
<p className="text-gray-500 text-sm">{t('dashboard.noRecentTx')}</p>
|
||||
<p className="text-gray-600 text-xs mt-1">{t('dashboard.historyAppears')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
@@ -792,7 +792,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium">
|
||||
{tx.direction === 'sent' ? 'Şandin' : 'Wergirtin'}
|
||||
{tx.direction === 'sent' ? t('dashboard.sent') : t('dashboard.received')}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">Block #{tx.blockNumber}</div>
|
||||
</div>
|
||||
@@ -852,7 +852,9 @@ export function WalletDashboard({ onDisconnect }: Props) {
|
||||
{isStakingSelectorOpen && (
|
||||
<div className="fixed inset-0 z-50 bg-black/80 flex items-end justify-center">
|
||||
<div className="bg-background w-full max-w-lg rounded-t-3xl p-6 animate-in slide-in-from-bottom duration-300">
|
||||
<h2 className="text-lg font-semibold mb-4 text-center">Staking Hilbijêre</h2>
|
||||
<h2 className="text-lg font-semibold mb-4 text-center">
|
||||
{t('dashboard.selectStaking')}
|
||||
</h2>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -865,8 +867,10 @@ export function WalletDashboard({ onDisconnect }: Props) {
|
||||
<Coins className="w-6 h-6 text-green-400" />
|
||||
</div>
|
||||
<div className="font-medium">HEZ Staking</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">Validator nominate bike</div>
|
||||
<div className="text-xs text-green-400 mt-2">Trust Score +</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
{t('dashboard.validatorNominate')}
|
||||
</div>
|
||||
<div className="text-xs text-green-400 mt-2">{t('dashboard.trustScorePlus')}</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -880,8 +884,10 @@ export function WalletDashboard({ onDisconnect }: Props) {
|
||||
<Droplets className="w-6 h-6 text-purple-400" />
|
||||
</div>
|
||||
<div className="font-medium">LP Staking</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">LP token stake bike</div>
|
||||
<div className="text-xs text-purple-400 mt-2">PEZ Xelat +</div>
|
||||
<div className="text-xs text-muted-foreground mt-1">
|
||||
{t('dashboard.lpStakeDesc')}
|
||||
</div>
|
||||
<div className="text-xs text-purple-400 mt-2">{t('dashboard.pezRewardPlus')}</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -889,7 +895,7 @@ export function WalletDashboard({ onDisconnect }: Props) {
|
||||
onClick={() => setIsStakingSelectorOpen(false)}
|
||||
className="w-full mt-4 py-3 bg-secondary text-muted-foreground rounded-xl"
|
||||
>
|
||||
Paşve
|
||||
{t('dashboard.goBack')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -948,6 +954,7 @@ const SEND_TOKENS: TokenOption[] = [
|
||||
function SendTab({ onBack }: { onBack: () => void }) {
|
||||
const { balance, api, assetHubApi, keypair } = useWallet();
|
||||
const { hapticNotification, hapticImpact } = useTelegram();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [selectedToken, setSelectedToken] = useState<SendToken | null>(null);
|
||||
const [toAddress, setToAddress] = useState('');
|
||||
@@ -1013,11 +1020,11 @@ function SendTab({ onBack }: { onBack: () => void }) {
|
||||
const tg = window.Telegram?.WebApp;
|
||||
|
||||
if (!tg?.showScanQrPopup) {
|
||||
setError('QR okuyucu vê platformê de amade nîne');
|
||||
setError(t('send.qrNotAvailable'));
|
||||
return;
|
||||
}
|
||||
|
||||
tg.showScanQrPopup({ text: 'Navnîşana cîzdanê bişoxîne' }, (scannedText: string) => {
|
||||
tg.showScanQrPopup({ text: t('send.scanQrText') }, (scannedText: string) => {
|
||||
if (scannedText) {
|
||||
// Extract address - might be raw address or URI format
|
||||
let address = scannedText;
|
||||
@@ -1035,7 +1042,7 @@ function SendTab({ onBack }: { onBack: () => void }) {
|
||||
tg.closeScanQrPopup?.();
|
||||
return true; // Close popup
|
||||
} else {
|
||||
setError('Navnîşana derbasdar nîne');
|
||||
setError(t('send.invalidAddress'));
|
||||
hapticNotification('error');
|
||||
}
|
||||
}
|
||||
@@ -1045,17 +1052,17 @@ function SendTab({ onBack }: { onBack: () => void }) {
|
||||
|
||||
const handleSend = async () => {
|
||||
if (!keypair) {
|
||||
setError('Cîzdan amade nîne');
|
||||
setError(t('send.walletNotReady'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedToken) {
|
||||
setError('Token hilbijêre');
|
||||
setError(t('send.selectTokenFirst'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!toAddress || !amount) {
|
||||
setError('Navnîşan û mîqdar binivîse');
|
||||
setError(t('send.fillAddressAndAmount'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1063,20 +1070,20 @@ function SendTab({ onBack }: { onBack: () => void }) {
|
||||
const sendAmount = parseFloat(amount);
|
||||
|
||||
if (sendAmount > currentBalance) {
|
||||
setError('Bakiye têrê nake');
|
||||
setError(t('send.insufficientBalance'));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if appropriate API is available
|
||||
if (selectedToken === 'HEZ' && !api) {
|
||||
setError('Mainnet API amade nîne');
|
||||
setError(t('send.mainnetApiNotReady'));
|
||||
return;
|
||||
}
|
||||
if (
|
||||
(selectedToken === 'PEZ' || selectedToken === 'USDT' || selectedToken === 'DOT') &&
|
||||
!assetHubApi
|
||||
) {
|
||||
setError('Asset Hub API amade nîne');
|
||||
setError(t('send.assetHubApiNotReady'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1106,7 +1113,7 @@ function SendTab({ onBack }: { onBack: () => void }) {
|
||||
setSuccess(true);
|
||||
hapticNotification('success');
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Transfer neserketî');
|
||||
setError(err instanceof Error ? err.message : t('send.transferFailed'));
|
||||
hapticNotification('error');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -1119,13 +1126,13 @@ function SendTab({ onBack }: { onBack: () => void }) {
|
||||
<div className="p-4 space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<button onClick={onBack} className="text-muted-foreground">
|
||||
← Paş
|
||||
{t('send.back')}
|
||||
</button>
|
||||
<h2 className="text-lg font-semibold">Token Hilbijêre</h2>
|
||||
<h2 className="text-lg font-semibold">{t('send.selectToken')}</h2>
|
||||
<div className="w-10" />
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground text-center">Kîjan token bişîne?</p>
|
||||
<p className="text-sm text-muted-foreground text-center">{t('send.whichToken')}</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
{SEND_TOKENS.map((token) => {
|
||||
@@ -1170,7 +1177,7 @@ function SendTab({ onBack }: { onBack: () => void }) {
|
||||
</div>
|
||||
|
||||
{(!api || !assetHubApi) && (
|
||||
<p className="text-xs text-yellow-500 text-center">⚠️ Hinek chain girêdayî nîne</p>
|
||||
<p className="text-xs text-yellow-500 text-center">{t('send.someChainNotConnected')}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -1183,9 +1190,9 @@ function SendTab({ onBack }: { onBack: () => void }) {
|
||||
<Check className="w-10 h-10 text-green-500" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold mb-2">Transfer Serketî!</h2>
|
||||
<h2 className="text-xl font-semibold mb-2">{t('send.transferSuccess')}</h2>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{amount} {selectedToken} hat şandin
|
||||
{t('send.wasSent', { amount, token: selectedToken || '' })}
|
||||
</p>
|
||||
</div>
|
||||
{txHash && (
|
||||
@@ -1198,7 +1205,7 @@ function SendTab({ onBack }: { onBack: () => void }) {
|
||||
onClick={onBack}
|
||||
className="w-full py-3 bg-primary text-primary-foreground rounded-xl font-semibold"
|
||||
>
|
||||
Temam
|
||||
{t('send.done')}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -1210,9 +1217,11 @@ function SendTab({ onBack }: { onBack: () => void }) {
|
||||
<div className="p-4 space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<button onClick={() => setSelectedToken(null)} className="text-muted-foreground">
|
||||
← Paş
|
||||
{t('send.back')}
|
||||
</button>
|
||||
<h2 className="text-lg font-semibold">Bişîne {selectedToken}</h2>
|
||||
<h2 className="text-lg font-semibold">
|
||||
{t('send.sendToken', { token: selectedToken || '' })}
|
||||
</h2>
|
||||
<div className="w-10" />
|
||||
</div>
|
||||
|
||||
@@ -1231,13 +1240,13 @@ function SendTab({ onBack }: { onBack: () => void }) {
|
||||
onClick={() => setSelectedToken(null)}
|
||||
className="text-xs text-primary hover:underline"
|
||||
>
|
||||
Biguhere
|
||||
{t('send.changeToken')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm text-muted-foreground">Navnîşana Wergir</label>
|
||||
<label className="text-sm text-muted-foreground">{t('send.recipientAddress')}</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
@@ -1249,7 +1258,7 @@ function SendTab({ onBack }: { onBack: () => void }) {
|
||||
<button
|
||||
onClick={handleScanQR}
|
||||
className="px-4 py-3 bg-cyan-600 hover:bg-cyan-700 rounded-xl flex items-center justify-center"
|
||||
title="QR Bişoxîne"
|
||||
title={t('send.scanQr')}
|
||||
>
|
||||
<ScanLine className="w-5 h-5" />
|
||||
</button>
|
||||
@@ -1258,9 +1267,9 @@ function SendTab({ onBack }: { onBack: () => void }) {
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between">
|
||||
<label className="text-sm text-muted-foreground">Mîqdar (HEZ)</label>
|
||||
<label className="text-sm text-muted-foreground">{t('send.amount')}</label>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Bakiye: {getCurrentBalance()} {selectedToken}
|
||||
{t('send.balanceLabel')} {getCurrentBalance()} {selectedToken}
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
@@ -1287,12 +1296,12 @@ function SendTab({ onBack }: { onBack: () => void }) {
|
||||
{isLoading ? (
|
||||
<>
|
||||
<RefreshCw className="w-4 h-4 animate-spin" />
|
||||
Tê şandin...
|
||||
{t('send.sending')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send className="w-4 h-4" />
|
||||
Bişîne
|
||||
{t('send.sendButton')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
@@ -1307,6 +1316,7 @@ function ReceiveTab({ address, onBack }: { address: string | null; onBack: () =>
|
||||
const [qrCodeUrl, setQrCodeUrl] = useState<string | null>(null);
|
||||
const [qrError, setQrError] = useState(false);
|
||||
const { hapticNotification } = useTelegram();
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Generate QR code when address changes
|
||||
useEffect(() => {
|
||||
@@ -1344,9 +1354,9 @@ function ReceiveTab({ address, onBack }: { address: string | null; onBack: () =>
|
||||
<div className="p-4 space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<button onClick={onBack} className="text-muted-foreground">
|
||||
← Paş
|
||||
{t('receive.back')}
|
||||
</button>
|
||||
<h2 className="text-lg font-semibold">Werbigire</h2>
|
||||
<h2 className="text-lg font-semibold">{t('receive.title')}</h2>
|
||||
<div className="w-10" />
|
||||
</div>
|
||||
|
||||
@@ -1358,16 +1368,14 @@ function ReceiveTab({ address, onBack }: { address: string | null; onBack: () =>
|
||||
) : qrError ? (
|
||||
<div className="text-center">
|
||||
<QrCode className="w-16 h-16 text-gray-400 mx-auto mb-2" />
|
||||
<p className="text-xs text-gray-500">QR çênebû</p>
|
||||
<p className="text-xs text-gray-500">{t('receive.qrFailed')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="animate-pulse bg-gray-200 w-full h-full rounded-lg" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Ev navnîşana te ye. Ji bo wergirtina token vê navnîşanê parve bike.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">{t('receive.shareAddress')}</p>
|
||||
|
||||
<div className="p-4 bg-muted rounded-xl">
|
||||
<p className="font-mono text-sm break-all">{address}</p>
|
||||
@@ -1380,12 +1388,12 @@ function ReceiveTab({ address, onBack }: { address: string | null; onBack: () =>
|
||||
{copied ? (
|
||||
<>
|
||||
<Check className="w-4 h-4" />
|
||||
Hat kopîkirin!
|
||||
{t('receive.addressCopied')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="w-4 h-4" />
|
||||
Navnîşanê Kopî Bike
|
||||
{t('receive.copyAddress')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
@@ -1407,6 +1415,7 @@ function HistoryTab({
|
||||
onBack: () => void;
|
||||
}) {
|
||||
const { hapticImpact } = useTelegram();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const getDecimalsForAsset = (section: string, assetId?: string): number => {
|
||||
if (section === 'balances') return 12; // HEZ
|
||||
@@ -1433,9 +1442,9 @@ function HistoryTab({
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<button onClick={onBack} className="text-muted-foreground">
|
||||
← Paş
|
||||
{t('history.back')}
|
||||
</button>
|
||||
<h2 className="text-lg font-semibold">Dîroka Transaksiyonan</h2>
|
||||
<h2 className="text-lg font-semibold">{t('history.title')}</h2>
|
||||
<button
|
||||
onClick={() => {
|
||||
hapticImpact('light');
|
||||
@@ -1451,13 +1460,13 @@ function HistoryTab({
|
||||
{isLoading ? (
|
||||
<div className="text-center py-12">
|
||||
<RefreshCw className="w-10 h-10 text-gray-600 mx-auto mb-3 animate-spin" />
|
||||
<p className="text-gray-400">Tê barkirin...</p>
|
||||
<p className="text-gray-400">{t('history.loadingTx')}</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-400 mb-1">Transaksiyona tune</p>
|
||||
<p className="text-gray-600 text-sm">Dîroka te dê li vir xuya bibe</p>
|
||||
<p className="text-gray-400 mb-1">{t('history.noTransactions')}</p>
|
||||
<p className="text-gray-600 text-sm">{t('history.historyAppears')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
@@ -1479,7 +1488,7 @@ function HistoryTab({
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">
|
||||
{tx.direction === 'sent' ? 'Şandin' : 'Wergirtin'}
|
||||
{tx.direction === 'sent' ? t('history.sent') : t('history.received')}
|
||||
</div>
|
||||
<div className="text-xs text-gray-400">Block #{tx.blockNumber}</div>
|
||||
</div>
|
||||
@@ -1504,12 +1513,12 @@ function HistoryTab({
|
||||
<div className="pt-2 border-t border-border/50 space-y-1">
|
||||
{tx.direction === 'sent' ? (
|
||||
<div className="flex justify-between text-xs">
|
||||
<span className="text-gray-500">Ji bo:</span>
|
||||
<span className="text-gray-500">{t('history.to')}</span>
|
||||
<span className="font-mono text-gray-400">{formatAddress(tx.to || '')}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-between text-xs">
|
||||
<span className="text-gray-500">Ji:</span>
|
||||
<span className="text-gray-500">{t('history.from')}</span>
|
||||
<span className="font-mono text-gray-400">{formatAddress(tx.from)}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user