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:
2026-02-14 18:16:08 +03:00
parent 71f142b9f4
commit c4282f5870
23 changed files with 2294 additions and 253 deletions
+32 -24
View File
@@ -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 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 çê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>
+55 -48
View File
@@ -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 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>
</>
)}
+34 -28
View File
@@ -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>
)}
+42 -31
View File
@@ -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"> 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"> 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"> 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>
+15 -13
View File
@@ -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"> 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>
+19 -11
View File
@@ -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"> 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)}
+72 -63
View File
@@ -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"> 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 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" />
ş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 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"> 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 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>
)}