feat: enhance merchant panel with balance breakdown, payout history, and security improvements
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cookies } from 'next/headers';
|
import { cookies } from 'next/headers';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
import ApiKeyVisibilityToggle from '@/components/merchant/ApiKeyVisibilityToggle';
|
||||||
|
|
||||||
async function getMerchant(identifier: string) {
|
async function getMerchant(identifier: string) {
|
||||||
const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(identifier);
|
const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(identifier);
|
||||||
@@ -116,54 +117,82 @@ export default async function MerchantIntegrationPage(props: {
|
|||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-10">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-10">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-2">Merchant ID</label>
|
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-2">Public Merchant ID</label>
|
||||||
<div className="bg-gray-50 p-5 rounded-2xl border border-gray-100 flex items-center justify-between">
|
<div className="bg-gray-50 p-5 rounded-2xl border border-gray-100 flex items-center justify-between">
|
||||||
<code className="text-xs font-mono font-bold text-gray-600">{merchant.id}</code>
|
<code className="text-xs font-mono font-bold text-gray-600 truncate">{merchant.id}</code>
|
||||||
<Copy size={16} className="text-gray-300 cursor-pointer hover:text-blue-600 transition" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-2">API Secret Key</label>
|
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest ml-2">Secure API Secret Key</label>
|
||||||
<div className="bg-gray-50 p-5 rounded-2xl border border-gray-100 flex items-center justify-between">
|
<ApiKeyVisibilityToggle apiKey={merchant.api_key} />
|
||||||
<code className="text-xs font-mono font-bold text-gray-600">
|
|
||||||
{merchant.api_key.substring(0, 8)}••••••••••••••••••••••••
|
|
||||||
</code>
|
|
||||||
<button className="text-[10px] font-black text-blue-600 uppercase tracking-widest hover:underline">Anahtarı Göster</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Webhook Settings */}
|
{/* Webhook & Payout Addresses */}
|
||||||
<div className="bg-white p-12 rounded-[48px] border border-gray-100 shadow-sm space-y-12">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-10">
|
||||||
<div className="flex items-center gap-6">
|
{/* Webhook Settings */}
|
||||||
<div className="w-14 h-14 bg-purple-50 rounded-2xl flex items-center justify-center text-purple-600">
|
<div className="bg-white p-12 rounded-[48px] border border-gray-100 shadow-sm space-y-12">
|
||||||
<Webhook size={28} />
|
<div className="flex items-center gap-6">
|
||||||
|
<div className="w-14 h-14 bg-purple-50 rounded-2xl flex items-center justify-center text-purple-600">
|
||||||
|
<Webhook size={28} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-2xl font-black text-gray-900">Webhooks</h3>
|
||||||
|
<p className="text-xs text-gray-400 font-bold uppercase tracking-widest mt-1">Anlık Bildirimler</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<h3 className="text-2xl font-black text-gray-900">Olay Bildirimleri (Webhooks)</h3>
|
<div className="space-y-8">
|
||||||
<p className="text-xs text-gray-400 font-bold uppercase tracking-widest mt-1">Ödeme sonuçlarını anlık olarak sunucunuzda karşılayın</p>
|
<div className="p-8 bg-gray-50 rounded-[32px] border border-gray-100 space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-[11px] font-black text-gray-400 uppercase tracking-widest ml-2">URL</span>
|
||||||
|
<span className={`text-[10px] font-black px-3 py-1 rounded-full ${merchant.webhook_url ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700'}`}>
|
||||||
|
{merchant.webhook_url ? 'AKTİF' : 'TANIMSZ'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-6 rounded-2xl border border-gray-200">
|
||||||
|
<code className="text-sm font-bold text-gray-700 break-all">
|
||||||
|
{merchant.webhook_url || 'https://siteniz.com/callback'}
|
||||||
|
</code>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-8">
|
{/* Payout Addresses */}
|
||||||
<p className="text-gray-500 font-medium leading-relaxed max-w-2xl">
|
<div className="bg-white p-12 rounded-[48px] border border-gray-100 shadow-sm space-y-12">
|
||||||
İşlem tamamlandığında sistemimiz belirttiğiniz URL'ye <code className="bg-gray-100 px-2 py-1 rounded text-blue-600 font-bold">POST</code> isteği gönderir. Bu isteğin içerisinde işlemin tüm detayları yer alır.
|
<div className="flex items-center gap-6">
|
||||||
</p>
|
<div className="w-14 h-14 bg-emerald-50 rounded-2xl flex items-center justify-center text-emerald-600">
|
||||||
|
<Zap size={28} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-2xl font-black text-gray-900">Hak Ediş Adresleriniz</h3>
|
||||||
|
<p className="text-xs text-gray-400 font-bold uppercase tracking-widest mt-1">Ödemeleriniz bu adreslere iletilir</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="p-8 bg-gray-50 rounded-[32px] border border-gray-100 space-y-6">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between">
|
{['EVM', 'SOLANA', 'TRON', 'BITCOIN'].map((net) => {
|
||||||
<span className="text-[11px] font-black text-gray-400 uppercase tracking-widest ml-2">Webhook URL</span>
|
const addr = merchant.payout_addresses?.[net] || (net === 'EVM' ? merchant.payout_address : null);
|
||||||
<span className={`text-[10px] font-black px-3 py-1 rounded-full ${merchant.webhook_url ? 'bg-emerald-100 text-emerald-700' : 'bg-red-100 text-red-700'}`}>
|
return (
|
||||||
{merchant.webhook_url ? 'HİZMETE HAZIR' : 'HENÜZ TANIMLANMAMIŞ'}
|
<div key={net} className="flex items-center justify-between p-4 bg-gray-50 rounded-2xl border border-gray-100 group">
|
||||||
</span>
|
<div className="flex items-center gap-3">
|
||||||
</div>
|
<div className={`w-8 h-8 rounded-xl flex items-center justify-center text-white text-[10px] font-black ${net === 'SOLANA' ? 'bg-emerald-500' : net === 'POLYGON' ? 'bg-purple-500' : net === 'TRON' ? 'bg-red-500' : 'bg-orange-500'}`}>
|
||||||
<div className="bg-white p-6 rounded-2xl border border-gray-200">
|
{net.slice(0, 1)}
|
||||||
<code className="text-sm font-bold text-gray-700 break-all">
|
</div>
|
||||||
{merchant.webhook_url || 'https://siteniz.com/api/payment-callback'}
|
<div>
|
||||||
</code>
|
<p className="text-[9px] font-black text-gray-400 uppercase tracking-[0.2em]">{net}</p>
|
||||||
</div>
|
<p className="text-[11px] font-mono font-bold text-gray-900 truncate max-w-[180px] lg:max-w-[250px]">
|
||||||
|
{addr || 'TANIMLANMAMIŞ'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{addr && <Copy size={14} className="text-gray-300 cursor-pointer hover:text-blue-600 transition" />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import {
|
|||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
Calendar,
|
Calendar,
|
||||||
ArrowUpRight,
|
ArrowUpRight,
|
||||||
Search
|
Search,
|
||||||
|
ShieldCheck
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { tr } from 'date-fns/locale';
|
import { tr } from 'date-fns/locale';
|
||||||
@@ -65,6 +66,17 @@ async function getMerchantData(identifier: string) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fetch merchant balances
|
||||||
|
const bResult = await db.query(
|
||||||
|
'SELECT network, token, balance, withdrawn FROM merchant_balances WHERE merchant_id = $1',
|
||||||
|
[id]
|
||||||
|
);
|
||||||
|
const balances = bResult.rows.map(r => ({
|
||||||
|
network: r.network,
|
||||||
|
token: r.token,
|
||||||
|
amount: parseFloat(r.balance) - parseFloat(r.withdrawn)
|
||||||
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
merchant,
|
merchant,
|
||||||
transactions,
|
transactions,
|
||||||
@@ -72,7 +84,8 @@ async function getMerchantData(identifier: string) {
|
|||||||
successfulCount,
|
successfulCount,
|
||||||
successRate,
|
successRate,
|
||||||
totalCount,
|
totalCount,
|
||||||
chartData
|
chartData,
|
||||||
|
balances
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +118,7 @@ export default async function MerchantDashboardPage(props: {
|
|||||||
redirect(`/merchant/${identifier}/login`);
|
redirect(`/merchant/${identifier}/login`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { merchant, transactions, totalRevenue, successfulCount, successRate, totalCount, chartData } = data;
|
const { merchant, transactions, totalRevenue, successfulCount, successRate, totalCount, chartData, balances } = data;
|
||||||
const recentTransactions = transactions.slice(0, 8);
|
const recentTransactions = transactions.slice(0, 8);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -118,7 +131,12 @@ export default async function MerchantDashboardPage(props: {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-black text-gray-900 tracking-tight">{merchant.name}</h1>
|
<h1 className="text-3xl font-black text-gray-900 tracking-tight">{merchant.name}</h1>
|
||||||
<p className="text-[10px] text-gray-400 font-black uppercase tracking-widest mt-1">Hoş Geldiniz, İşlemlerinizi Buradan Takip Edebilirsiniz</p>
|
<div className="flex items-center gap-3 mt-1">
|
||||||
|
<p className="text-[10px] text-gray-400 font-black uppercase tracking-widest">Firma Yönetim Paneli</p>
|
||||||
|
<span className="px-2 py-0.5 bg-emerald-50 text-emerald-600 text-[9px] font-black rounded-lg border border-emerald-100 uppercase tracking-tight">
|
||||||
|
Komisyon: %{merchant.fee_percent || '1.0'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
@@ -132,34 +150,80 @@ export default async function MerchantDashboardPage(props: {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* On-chain Vault Section */}
|
{/* Balances & Vaults Section */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
<div className="bg-white p-8 rounded-[40px] border border-gray-100 shadow-sm flex items-center gap-6 group hover:border-blue-500 transition-all">
|
{/* Crypto Balances */}
|
||||||
<div className="w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center text-blue-600 shrink-0 group-hover:bg-blue-600 group-hover:text-white transition-colors">
|
<div className="lg:col-span-1 bg-gray-900 rounded-[40px] p-8 shadow-2xl relative overflow-hidden flex flex-col justify-between">
|
||||||
<Wallet size={24} />
|
<div className="relative z-10">
|
||||||
</div>
|
<div className="flex items-center gap-3 mb-6">
|
||||||
<div className="min-w-0 flex-1">
|
<div className="w-10 h-10 bg-white/10 rounded-xl flex items-center justify-center text-emerald-400">
|
||||||
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-1">EVM Kasanız (Polygon/BSC/ETH)</p>
|
<Wallet size={20} />
|
||||||
<div className="flex items-center gap-2">
|
</div>
|
||||||
<span className="text-xs font-mono font-bold text-gray-900 truncate">
|
<h3 className="text-lg font-black text-white uppercase tracking-tight">Mevcut Bakiyeleriniz</h3>
|
||||||
{merchant.evm_vault_address || 'Henüz Oluşturulmadı'}
|
|
||||||
</span>
|
|
||||||
<div className="px-2 py-0.5 bg-gray-100 rounded text-[9px] font-black text-gray-400 uppercase tracking-tighter">Copy</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
{balances && balances.length > 0 ? balances.map((b: any, i: number) => (
|
||||||
|
<div key={i} className="flex items-center justify-between p-4 bg-white/5 rounded-2xl border border-white/5 group hover:bg-white/10 transition-colors">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className={`w-2 h-2 rounded-full ${b.network === 'SOLANA' ? 'bg-emerald-400' : b.network === 'POLYGON' ? 'bg-purple-400' : b.network === 'TRON' ? 'bg-red-400' : 'bg-orange-400'}`}></div>
|
||||||
|
<div>
|
||||||
|
<p className="text-[9px] font-black text-white/40 uppercase tracking-widest">{b.network}</p>
|
||||||
|
<p className="text-sm font-black text-white uppercase">{b.token}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<p className="text-lg font-black text-white tabular-nums">{b.amount.toFixed(4)}</p>
|
||||||
|
<p className="text-[9px] font-black text-emerald-400 uppercase tracking-tighter">Çekilebilir</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)) : (
|
||||||
|
<div className="py-10 text-center">
|
||||||
|
<p className="text-xs font-bold text-white/20 uppercase tracking-widest">Henüz birikmiş bakiye yok</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 pt-6 border-t border-white/10 relative z-10">
|
||||||
|
<p className="text-[11px] font-black text-gray-500 uppercase tracking-widest mb-2">Dönüşüm Özeti</p>
|
||||||
|
<h4 className="text-3xl font-black text-emerald-400">
|
||||||
|
{totalRevenue.toLocaleString('tr-TR', { minimumFractionDigits: 2 })} <span className="text-base">₺</span>
|
||||||
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white p-8 rounded-[40px] border border-gray-100 shadow-sm flex items-center gap-6 group hover:border-purple-500 transition-all">
|
{/* Vault Addresses */}
|
||||||
<div className="w-16 h-16 bg-purple-50 rounded-2xl flex items-center justify-center text-purple-600 shrink-0 group-hover:bg-purple-600 group-hover:text-white transition-colors">
|
<div className="lg:col-span-2 grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
<Wallet size={24} />
|
<div className="bg-white p-10 rounded-[40px] border border-gray-100 shadow-sm flex flex-col justify-between group hover:border-blue-500 transition-all">
|
||||||
|
<div>
|
||||||
|
<div className="w-16 h-16 bg-blue-50 rounded-2xl flex items-center justify-center text-blue-600 mb-6 group-hover:bg-blue-600 group-hover:text-white transition-colors">
|
||||||
|
<ShieldCheck size={28} />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-black text-gray-900 mb-2">EVM Kasanız</h3>
|
||||||
|
<p className="text-xs text-gray-400 font-bold uppercase tracking-widest mb-6">Polygon / BSC / Ethereum Ödemeleri İçin</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-gray-50 rounded-2xl border border-gray-100">
|
||||||
|
<p className="text-[9px] font-black text-gray-400 uppercase tracking-widest mb-2">Adres</p>
|
||||||
|
<p className="font-mono text-xs font-bold text-gray-900 break-all leading-relaxed">
|
||||||
|
{merchant.evm_vault_address || 'Henüz Oluşturulmadı'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
|
||||||
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-1">Solana Kasanız</p>
|
<div className="bg-white p-10 rounded-[40px] border border-gray-100 shadow-sm flex flex-col justify-between group hover:border-emerald-500 transition-all">
|
||||||
<div className="flex items-center gap-2">
|
<div>
|
||||||
<span className="text-xs font-mono font-bold text-gray-900 truncate">
|
<div className="w-16 h-16 bg-emerald-50 rounded-2xl flex items-center justify-center text-emerald-600 mb-6 group-hover:bg-emerald-600 group-hover:text-white transition-colors">
|
||||||
{merchant.sol_vault_address || 'Henüz Oluşturulmadı'}
|
<ShieldCheck size={28} />
|
||||||
</span>
|
</div>
|
||||||
<div className="px-2 py-0.5 bg-gray-100 rounded text-[9px] font-black text-gray-400 uppercase tracking-tighter">Copy</div>
|
<h3 className="text-xl font-black text-gray-900 mb-2">Solana Kasanız</h3>
|
||||||
|
<p className="text-xs text-gray-400 font-bold uppercase tracking-widest mb-6">Solana / SPL Token Ödemeleri İçin</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-gray-50 rounded-2xl border border-gray-100">
|
||||||
|
<p className="text-[9px] font-black text-gray-400 uppercase tracking-widest mb-2">Adres</p>
|
||||||
|
<p className="font-mono text-xs font-bold text-gray-900 break-all leading-relaxed">
|
||||||
|
{merchant.sol_vault_address || 'Henüz Oluşturulmadı'}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
181
app/merchant/[id]/(dashboard)/payouts/page.tsx
Normal file
181
app/merchant/[id]/(dashboard)/payouts/page.tsx
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { db } from '@/lib/db';
|
||||||
|
import {
|
||||||
|
Wallet,
|
||||||
|
ExternalLink,
|
||||||
|
Clock,
|
||||||
|
ArrowUpRight,
|
||||||
|
Search
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { format } from 'date-fns';
|
||||||
|
import { tr } from 'date-fns/locale';
|
||||||
|
import { cookies } from 'next/headers';
|
||||||
|
import { redirect } from 'next/navigation';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
async function getMerchantPayouts(identifier: string) {
|
||||||
|
const isUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(identifier);
|
||||||
|
let resolvedId = identifier;
|
||||||
|
|
||||||
|
if (!isUUID) {
|
||||||
|
const result = await db.query('SELECT id FROM merchants WHERE short_id = $1 LIMIT 1', [identifier]);
|
||||||
|
const merchant = result.rows[0];
|
||||||
|
if (merchant) {
|
||||||
|
resolvedId = merchant.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { rows } = await db.query(`
|
||||||
|
SELECT * FROM payouts
|
||||||
|
WHERE merchant_id = $1
|
||||||
|
ORDER BY created_at DESC
|
||||||
|
`, [resolvedId]);
|
||||||
|
return { payouts: rows, merchantId: resolvedId };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Merchant payout history fetch error:', error);
|
||||||
|
return { payouts: [], merchantId: resolvedId };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function MerchantPayoutsPage(props: {
|
||||||
|
params: Promise<{ id: string }>;
|
||||||
|
}) {
|
||||||
|
const resolvedParams = await props.params;
|
||||||
|
const identifier = resolvedParams.id;
|
||||||
|
const { payouts, merchantId } = await getMerchantPayouts(identifier);
|
||||||
|
const cookieStore = await cookies();
|
||||||
|
|
||||||
|
if (!cookieStore.get(`merchant_auth_${merchantId}`)) {
|
||||||
|
redirect(`/merchant/${identifier}/login`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-10 animate-in fade-in slide-in-from-bottom-4 duration-700 pb-20 text-sans tracking-tight">
|
||||||
|
{/* Header Area */}
|
||||||
|
<div className="bg-white p-10 rounded-[40px] border border-gray-100 shadow-sm flex flex-col md:flex-row md:items-center justify-between gap-8">
|
||||||
|
<div className="flex items-center gap-6">
|
||||||
|
<div className="w-16 h-16 bg-blue-600 rounded-[24px] flex items-center justify-center text-white shadow-lg shadow-blue-100">
|
||||||
|
<Wallet size={28} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-black text-gray-900 tracking-tight">Ödemelerim</h1>
|
||||||
|
<p className="text-[10px] text-gray-400 font-black uppercase tracking-widest mt-1">Sistemden cüzdanınıza yapılan transfer geçmişi</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stats Overview */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||||
|
<div className="bg-white p-8 rounded-[40px] border border-gray-100 shadow-sm flex items-center gap-6">
|
||||||
|
<div className="w-14 h-14 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 pl-1">Toplam Çekim</p>
|
||||||
|
<p className="text-2xl font-black text-gray-900">{payouts.length}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white p-8 rounded-[40px] border border-gray-100 shadow-sm flex items-center gap-6">
|
||||||
|
<div className="w-14 h-14 bg-blue-50 rounded-2xl flex items-center justify-center text-blue-600">
|
||||||
|
<Clock size={24} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest pl-1">Son Çekim</p>
|
||||||
|
<p className="text-2xl 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-8 rounded-[40px] border border-gray-100 shadow-sm flex items-center gap-6">
|
||||||
|
<div className="w-14 h-14 bg-orange-50 rounded-2xl flex items-center justify-center text-orange-600">
|
||||||
|
<Wallet size={24} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-[10px] font-black text-gray-400 uppercase tracking-widest pl-1">Durum</p>
|
||||||
|
<p className="text-2xl font-black text-gray-900 uppercase">Aktif</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Payouts Table */}
|
||||||
|
<div className="bg-white rounded-[40px] border border-gray-100 shadow-sm overflow-hidden overflow-x-auto">
|
||||||
|
<table className="w-full text-left">
|
||||||
|
<thead>
|
||||||
|
<tr className="bg-gray-50/50 text-gray-400 text-[10px] font-black uppercase tracking-[0.2em] border-b border-gray-50">
|
||||||
|
<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 items-center gap-2">
|
||||||
|
<span className="text-lg font-black text-gray-900">{Number(p.amount).toFixed(4)}</span>
|
||||||
|
<span className="text-xs text-gray-400 font-bold uppercase">{p.currency}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-10 py-8">
|
||||||
|
<span className={`px-2 py-0.5 rounded-lg text-[10px] font-black uppercase tracking-wider ${
|
||||||
|
p.network === 'SOLANA' ? 'bg-emerald-50 text-emerald-600' :
|
||||||
|
p.network === 'TRON' ? 'bg-red-50 text-red-600' :
|
||||||
|
'bg-blue-50 text-blue-600'
|
||||||
|
}`}>
|
||||||
|
{p.network}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-10 py-8 font-mono text-xs text-gray-500">
|
||||||
|
<div className="flex items-center gap-1.5 bg-gray-50 px-3 py-1.5 rounded-xl w-fit">
|
||||||
|
{p.destination_address.slice(0, 6)}...{p.destination_address.slice(-6)}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="px-10 py-8 text-xs font-bold text-gray-500 uppercase">
|
||||||
|
{format(new Date(p.created_at), 'dd MMM yyyy, HH:mm', { locale: tr })}
|
||||||
|
</td>
|
||||||
|
<td className="px-10 py-8 text-center">
|
||||||
|
<span className={`inline-flex items-center px-4 py-1 rounded-full text-[10px] font-black uppercase tracking-widest ${p.status === 'succeeded' ? 'bg-emerald-50 text-emerald-600' : 'bg-red-50 text-red-600'}`}>
|
||||||
|
{p.status === 'succeeded' ? 'Başarılı' : 'Hatalı'}
|
||||||
|
</span>
|
||||||
|
</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"
|
||||||
|
>
|
||||||
|
GÖRÜNTÜLE
|
||||||
|
<ExternalLink size={14} />
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
<span className="text-[10px] font-black text-gray-300 uppercase italic">Kayıt Yok</span>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{payouts.length === 0 && (
|
||||||
|
<div className="p-20 text-center space-y-6">
|
||||||
|
<div className="w-20 h-20 bg-gray-50 rounded-[32px] flex items-center justify-center mx-auto text-gray-200">
|
||||||
|
<Wallet size={40} />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-gray-900 font-black uppercase tracking-tight text-lg">Henüz Ödeme Yapılmadı</p>
|
||||||
|
<p className="text-gray-400 font-bold uppercase tracking-widest text-[10px] max-w-xs mx-auto">Sistemimizden cüzdanınıza henüz bir kripto transferi gerçekleşmedi.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
44
components/merchant/ApiKeyVisibilityToggle.tsx
Normal file
44
components/merchant/ApiKeyVisibilityToggle.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Copy, Check, Eye, EyeOff } from 'lucide-react';
|
||||||
|
|
||||||
|
interface ApiKeyVisibilityToggleProps {
|
||||||
|
apiKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ApiKeyVisibilityToggle({ apiKey }: ApiKeyVisibilityToggleProps) {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const handleCopy = () => {
|
||||||
|
navigator.clipboard.writeText(apiKey);
|
||||||
|
setCopied(true);
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-gray-50 p-5 rounded-2xl border border-gray-100 flex items-center justify-between gap-4">
|
||||||
|
<code className="text-xs font-mono font-bold text-gray-600 truncate flex-1">
|
||||||
|
{isVisible ? apiKey : `${apiKey.substring(0, 8)}${'•'.repeat(24)}`}
|
||||||
|
</code>
|
||||||
|
<div className="flex items-center gap-3 shrink-0">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsVisible(!isVisible)}
|
||||||
|
className="p-2 hover:bg-white rounded-lg transition-colors text-gray-400 hover:text-blue-600"
|
||||||
|
title={isVisible ? "Gizle" : "Göster"}
|
||||||
|
>
|
||||||
|
{isVisible ? <EyeOff size={16} /> : <Eye size={16} />}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleCopy}
|
||||||
|
className="p-2 hover:bg-white rounded-lg transition-colors text-gray-400 hover:text-blue-600"
|
||||||
|
title="Kopyala"
|
||||||
|
>
|
||||||
|
{copied ? <Check size={16} className="text-emerald-500" /> : <Copy size={16} />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,7 +10,8 @@ import {
|
|||||||
Terminal,
|
Terminal,
|
||||||
Building2,
|
Building2,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
LogOut
|
LogOut,
|
||||||
|
Wallet
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
export default function MerchantSidebar({ merchantId }: { merchantId: string }) {
|
export default function MerchantSidebar({ merchantId }: { merchantId: string }) {
|
||||||
@@ -34,6 +35,7 @@ export default function MerchantSidebar({ merchantId }: { merchantId: string })
|
|||||||
const navItems = [
|
const navItems = [
|
||||||
{ label: 'Panel', icon: LayoutDashboard, href: `/merchant/${merchantId}` },
|
{ label: 'Panel', icon: LayoutDashboard, href: `/merchant/${merchantId}` },
|
||||||
{ label: 'İşlemler', icon: CreditCard, href: `/merchant/${merchantId}/transactions` },
|
{ label: 'İşlemler', icon: CreditCard, href: `/merchant/${merchantId}/transactions` },
|
||||||
|
{ label: 'Ödemeler', icon: Wallet, href: `/merchant/${merchantId}/payouts` },
|
||||||
{ label: 'Entegrasyon', icon: Terminal, href: `/merchant/${merchantId}/integration` },
|
{ label: 'Entegrasyon', icon: Terminal, href: `/merchant/${merchantId}/integration` },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user