Files
pezkuwi-telegram-miniapp/src/components/p2p/BalanceCard.tsx
T
pezkuwichain 31e768de45 feat: integrate P2P trading into Telegram mini app
- Add 8 Supabase edge functions for P2P operations (get-internal-balance,
  get-payment-methods, get-p2p-offers, accept-p2p-offer, get-p2p-trades,
  trade-action, p2p-messages, p2p-dispute)
- Add frontend P2P API layer (src/lib/p2p-api.ts)
- Add 8 P2P components (BalanceCard, OfferList, TradeModal, CreateOfferModal,
  TradeView, TradeChat, DisputeModal, P2P section)
- Embed P2P as internal section in App.tsx instead of external link
- Remove old P2PModal component
- Add ~70 P2P translation keys across all 6 languages
2026-02-26 18:38:12 +03:00

107 lines
3.6 KiB
TypeScript

import { useState, useEffect } from 'react';
import { Wallet, Lock, RefreshCw } from 'lucide-react';
import { cn } from '@/lib/utils';
import { useAuth } from '@/contexts/AuthContext';
import { useTranslation } from '@/i18n';
import { useTelegram } from '@/hooks/useTelegram';
import { getInternalBalance, type InternalBalance } from '@/lib/p2p-api';
interface BalanceCardProps {
onRefresh?: () => void;
}
export function BalanceCard({ onRefresh }: BalanceCardProps) {
const { sessionToken } = useAuth();
const { t, isRTL } = useTranslation();
const { hapticImpact } = useTelegram();
const [balances, setBalances] = useState<InternalBalance[]>([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const fetchBalances = async () => {
if (!sessionToken) return;
try {
const data = await getInternalBalance(sessionToken);
setBalances(data);
} catch (err) {
console.error('Failed to fetch balances:', err);
} finally {
setLoading(false);
setRefreshing(false);
}
};
useEffect(() => {
fetchBalances();
}, [sessionToken]);
const handleRefresh = () => {
hapticImpact('light');
setRefreshing(true);
fetchBalances();
onRefresh?.();
};
const formatBalance = (val: number) => {
return val.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 4 });
};
return (
<div
className={cn(
'bg-gradient-to-br from-cyan-500/20 to-blue-600/20 rounded-2xl border border-cyan-500/30 p-4',
isRTL && 'direction-rtl'
)}
dir={isRTL ? 'rtl' : 'ltr'}
>
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<Wallet className="w-5 h-5 text-cyan-400" />
<h3 className="text-sm font-semibold text-foreground">{t('p2p.internalBalance')}</h3>
</div>
<button
onClick={handleRefresh}
disabled={refreshing}
className="p-1.5 rounded-full hover:bg-muted/50 transition-colors"
>
<RefreshCw className={cn('w-4 h-4 text-muted-foreground', refreshing && 'animate-spin')} />
</button>
</div>
{loading ? (
<div className="flex items-center justify-center py-4">
<div className="w-5 h-5 border-2 border-cyan-400 border-t-transparent rounded-full animate-spin" />
</div>
) : balances.length === 0 ? (
<div className="text-center py-3">
<p className="text-xs text-muted-foreground">{t('p2p.noBalance')}</p>
</div>
) : (
<div className="space-y-2">
{balances.map((bal) => (
<div key={bal.token} className="bg-card/50 rounded-xl p-3">
<div className="flex items-center justify-between mb-1">
<span className="text-sm font-bold text-foreground">{bal.token}</span>
<span className="text-sm font-bold text-foreground">
{formatBalance(bal.available_balance + bal.locked_balance)}
</span>
</div>
<div className="flex items-center justify-between text-xs text-muted-foreground">
<span className="flex items-center gap-1">
<Wallet className="w-3 h-3" />
{t('p2p.available')}: {formatBalance(bal.available_balance)}
</span>
<span className="flex items-center gap-1">
<Lock className="w-3 h-3" />
{t('p2p.locked')}: {formatBalance(bal.locked_balance)}
</span>
</div>
</div>
))}
</div>
)}
</div>
);
}