mirror of
https://github.com/pezkuwichain/pezkuwi-telegram-miniapp.git
synced 2026-04-22 03:07:55 +00:00
31e768de45
- 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
107 lines
3.6 KiB
TypeScript
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>
|
|
);
|
|
}
|