feat: add Solana USDT/USDC support and refine admin payouts UI
This commit is contained in:
172
app/admin/payouts/page.tsx
Normal file
172
app/admin/payouts/page.tsx
Normal file
@@ -0,0 +1,172 @@
|
||||
import React from 'react';
|
||||
import { db } from '@/lib/db';
|
||||
import {
|
||||
Search,
|
||||
ExternalLink,
|
||||
Clock,
|
||||
Wallet,
|
||||
Link as LinkIcon,
|
||||
ChevronRight,
|
||||
ArrowUpRight
|
||||
} from 'lucide-react';
|
||||
import { format } from 'date-fns';
|
||||
import { tr } from 'date-fns/locale';
|
||||
|
||||
async function getPayouts() {
|
||||
try {
|
||||
const { rows } = await db.query(`
|
||||
SELECT p.*, m.name as merchant_name
|
||||
FROM payouts p
|
||||
LEFT JOIN merchants m ON p.merchant_id = m.id
|
||||
ORDER BY p.created_at DESC
|
||||
`);
|
||||
return rows;
|
||||
} catch (error) {
|
||||
console.error('Payouts fetch error:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export default async function PayoutsPage() {
|
||||
const payouts = await getPayouts();
|
||||
|
||||
return (
|
||||
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-700">
|
||||
{/* Header Area */}
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-black text-gray-900 tracking-tight">Ödemeler (Payouts)</h1>
|
||||
<p className="text-gray-500 font-medium mt-1 uppercase tracking-widest text-[10px]">Merchantlara yapılan kripto ödeme geçmişi</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Stats Overview (Optional) */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="bg-white p-6 rounded-[32px] border border-gray-100 shadow-sm flex items-center gap-5">
|
||||
<div className="w-12 h-12 bg-emerald-50 rounded-2xl flex items-center justify-center text-emerald-600">
|
||||
<ArrowUpRight size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest">Toplam Ödeme</p>
|
||||
<p className="text-xl font-black text-gray-900">{payouts.length}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white p-6 rounded-[32px] border border-gray-100 shadow-sm flex items-center gap-5">
|
||||
<div className="w-12 h-12 bg-blue-50 rounded-2xl flex items-center justify-center text-blue-600">
|
||||
<Wallet size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest">Son İşlem</p>
|
||||
<p className="text-xl font-black text-gray-900">
|
||||
{payouts.length > 0 ? format(new Date(payouts[0].created_at), 'dd MMM', { locale: tr }) : '-'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white p-6 rounded-[32px] border border-gray-100 shadow-sm flex items-center gap-5">
|
||||
<div className="w-12 h-12 bg-orange-50 rounded-2xl flex items-center justify-center text-orange-600">
|
||||
<Clock size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest">Durum</p>
|
||||
<p className="text-xl font-black text-gray-900">Aktif</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Payouts Table */}
|
||||
<div className="bg-white rounded-[40px] border border-gray-100 shadow-sm overflow-hidden text-sans tracking-tight">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full text-left">
|
||||
<thead>
|
||||
<tr className="bg-gray-50/30 text-gray-400 text-[10px] font-black uppercase tracking-[0.2em] border-b border-gray-50">
|
||||
<th className="px-10 py-6">Merchant</th>
|
||||
<th className="px-10 py-6">Miktar & Varlık</th>
|
||||
<th className="px-10 py-6">Ağ (Network)</th>
|
||||
<th className="px-10 py-6">Hedef Adres</th>
|
||||
<th className="px-10 py-6">Tarih</th>
|
||||
<th className="px-10 py-6 text-center">Durum</th>
|
||||
<th className="px-10 py-6 text-right">Blockchain</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-50">
|
||||
{payouts.map((p: any) => (
|
||||
<tr key={p.id} className="group hover:bg-gray-50/50 transition-colors">
|
||||
<td className="px-10 py-8">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xs font-black text-blue-600 uppercase tracking-wider">
|
||||
{p.merchant_name}
|
||||
</span>
|
||||
<span className="text-[9px] text-gray-400 font-bold mt-1">ID: {p.merchant_id.slice(0,8)}...</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-10 py-8 font-black text-gray-900">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg">{Number(p.amount).toFixed(4)}</span>
|
||||
<span className="text-xs text-gray-400 uppercase">{p.currency}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-10 py-8">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`px-2 py-0.5 rounded-lg text-[10px] font-black uppercase tracking-wider ${
|
||||
p.network === 'SOLANA' ? 'bg-purple-50 text-purple-600' :
|
||||
p.network === 'TRON' ? 'bg-red-50 text-red-600' :
|
||||
'bg-blue-50 text-blue-600'
|
||||
}`}>
|
||||
{p.network}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-10 py-8">
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-1.5 text-xs font-mono text-gray-500 bg-gray-50 px-3 py-1.5 rounded-xl w-fit">
|
||||
<Wallet size={12} className="text-gray-400" />
|
||||
{p.destination_address.slice(0, 8)}...{p.destination_address.slice(-8)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-10 py-8">
|
||||
<span className="text-xs font-bold text-gray-500">
|
||||
{format(new Date(p.created_at), 'dd MMM yyyy, HH:mm', { locale: tr })}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-10 py-8">
|
||||
<div className="flex justify-center">
|
||||
<span className={`inline-flex items-center px-4 py-1 rounded-full text-[10px] font-black uppercase tracking-wider ${p.status === 'succeeded' ? 'bg-emerald-50 text-emerald-600' : 'bg-red-50 text-red-600'}`}>
|
||||
{p.status === 'succeeded' ? 'Başarılı' : 'Hatalı'}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-10 py-8 text-right">
|
||||
{p.tx_hash && p.tx_hash !== 'mock' ? (
|
||||
<a
|
||||
href={p.network === 'SOLANA' ? `https://explorer.solana.com/tx/${p.tx_hash}` :
|
||||
p.network === 'TRON' ? `https://tronscan.org/#/transaction/${p.tx_hash}` :
|
||||
`https://polygonscan.com/tx/${p.tx_hash}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-2 text-blue-600 hover:text-blue-800 font-black text-[10px] uppercase tracking-widest transition-all p-2 bg-blue-50 rounded-xl"
|
||||
>
|
||||
TX GÖRÜNTÜLE
|
||||
<ExternalLink size={14} />
|
||||
</a>
|
||||
) : (
|
||||
<span className="text-[10px] font-black text-gray-300 uppercase tracking-widest italic">Mock İşlem</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{payouts.length === 0 && (
|
||||
<div className="p-20 text-center space-y-4">
|
||||
<div className="w-16 h-16 bg-gray-50 rounded-full flex items-center justify-center mx-auto text-gray-300">
|
||||
<Wallet size={32} />
|
||||
</div>
|
||||
<p className="text-gray-400 font-bold uppercase tracking-widest text-xs">Henüz ödeme yapılmadı</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user