Feature: Implemented Dynamic Admin Settings for Platform Addresses and Security Guidelines for Private Keys
This commit is contained in:
@@ -1,138 +1,188 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Globe,
|
||||
ShieldCheck,
|
||||
Bell,
|
||||
Trash2,
|
||||
Smartphone,
|
||||
Monitor,
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Zap,
|
||||
ShieldCheck,
|
||||
Save,
|
||||
RefreshCcw,
|
||||
Wallet,
|
||||
Settings,
|
||||
Server,
|
||||
Key,
|
||||
AlertCircle
|
||||
} from 'lucide-react';
|
||||
|
||||
export default function SettingsPage() {
|
||||
export default function AdminSettingsPage() {
|
||||
const [settings, setSettings] = useState({
|
||||
sol_platform_address: '',
|
||||
evm_platform_address: ''
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [message, setMessage] = useState({ type: '', text: '' });
|
||||
|
||||
useEffect(() => {
|
||||
fetchSettings();
|
||||
}, []);
|
||||
|
||||
const fetchSettings = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const res = await fetch('/api/admin/settings');
|
||||
const data = await res.json();
|
||||
if (data.error) throw new Error(data.error);
|
||||
setSettings({
|
||||
sol_platform_address: data.sol_platform_address || '',
|
||||
evm_platform_address: data.evm_platform_address || ''
|
||||
});
|
||||
} catch (err: any) {
|
||||
setMessage({ type: 'error', text: 'Ayarlar yüklenemedi: ' + err.message });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsSaving(true);
|
||||
setMessage({ type: '', text: '' });
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/admin/settings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(settings)
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.error) throw new Error(data.error);
|
||||
setMessage({ type: 'success', text: 'Ayarlar başarıyla güncellendi.' });
|
||||
} catch (err: any) {
|
||||
setMessage({ type: 'error', text: 'Kaydetme hatası: ' + err.message });
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl space-y-10 animate-in fade-in slide-in-from-bottom-4 duration-700">
|
||||
<div className="max-w-4xl mx-auto space-y-10 animate-in fade-in slide-in-from-bottom-4 duration-700">
|
||||
{/* Header */}
|
||||
<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">Ayarlar</h1>
|
||||
<p className="text-sm text-gray-400 font-bold uppercase tracking-widest mt-2">Platform tercihlerinizi yönetin</p>
|
||||
<h1 className="text-3xl font-black text-gray-900 tracking-tight italic">Platform Ayarları</h1>
|
||||
<p className="text-xs text-gray-400 font-bold uppercase tracking-widest mt-2 px-1">Sistem Genel Yapılandırması</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 px-6 py-2 bg-blue-50 rounded-full border border-blue-100">
|
||||
<ShieldCheck size={14} className="text-blue-600" />
|
||||
<span className="text-[10px] font-black text-blue-600 uppercase tracking-widest">Global Kontrol Ünitesi</span>
|
||||
</div>
|
||||
<button className="px-8 py-4 bg-gray-900 text-white rounded-2xl font-black text-sm shadow-xl shadow-gray-200 hover:bg-gray-800 transition active:scale-95">
|
||||
Değişiklikleri Kaydet
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-10">
|
||||
{/* Left Column: Sections */}
|
||||
<div className="lg:col-span-2 space-y-8">
|
||||
{/* General Section */}
|
||||
<section className="bg-white p-10 rounded-[40px] border border-gray-100 shadow-sm space-y-8">
|
||||
<div className="flex items-center gap-4 border-b border-gray-50 pb-6">
|
||||
<div className="w-12 h-12 bg-blue-50 rounded-2xl flex items-center justify-center text-blue-600">
|
||||
<Globe size={24} />
|
||||
{/* Main Form */}
|
||||
<form onSubmit={handleSave} className="space-y-8">
|
||||
{/* Platform Addresses Card */}
|
||||
<div className="bg-white p-10 rounded-[48px] border border-gray-100 shadow-sm space-y-10">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 bg-gray-50 rounded-2xl flex items-center justify-center text-gray-900 group-hover:bg-blue-600 group-hover:text-white transition-all">
|
||||
<Wallet size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-black text-gray-900">Merkezi Cüzdanlar (Platform)</h3>
|
||||
<p className="text-xs text-gray-400 font-bold uppercase tracking-widest mt-1">Süpürülen tüm fonların toplanacağı ana adresler</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-8">
|
||||
<div className="space-y-3">
|
||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-[0.2em] ml-1">Solana Platform Adresi</label>
|
||||
<div className="relative">
|
||||
<Server className="absolute left-5 top-1/2 -translate-y-1/2 text-gray-300" size={20} />
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={settings.sol_platform_address}
|
||||
onChange={(e) => setSettings({ ...settings, sol_platform_address: e.target.value })}
|
||||
placeholder="Solana Wallet Address"
|
||||
className="w-full pl-14 pr-6 py-5 bg-gray-50 border-2 border-transparent focus:border-purple-500 focus:bg-white rounded-[24px] outline-none transition-all font-bold text-gray-900 font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
<h2 className="text-xl font-black text-gray-900 uppercase tracking-tight">Genel Ayarlar</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div className="space-y-3">
|
||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest pl-2">Mağaza Adı</label>
|
||||
<input type="text" defaultValue="P2CGateway" className="w-full px-6 py-4 bg-gray-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-500 outline-none" />
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest pl-2">Destek E-postası</label>
|
||||
<input type="email" defaultValue="support@ayris.dev" className="w-full px-6 py-4 bg-gray-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-500 outline-none" />
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest pl-2">Para Birimi</label>
|
||||
<select className="w-full px-6 py-4 bg-gray-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-500 outline-none appearance-none">
|
||||
<option>Türk Lirası (₺)</option>
|
||||
<option>US Dollar ($)</option>
|
||||
<option>Euro (€)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest pl-2">Zaman Dilimi</label>
|
||||
<select className="w-full px-6 py-4 bg-gray-50 border-none rounded-2xl text-sm font-bold focus:ring-2 focus:ring-blue-500 outline-none appearance-none">
|
||||
<option>Istanbul (GMT+3)</option>
|
||||
<option>London (GMT+0)</option>
|
||||
<option>New York (EST)</option>
|
||||
</select>
|
||||
<div className="space-y-3">
|
||||
<label className="text-[10px] font-black text-gray-400 uppercase tracking-[0.2em] ml-1">EVM Platform Adresi (Polygon/BSC/ETH)</label>
|
||||
<div className="relative">
|
||||
<Server className="absolute left-5 top-1/2 -translate-y-1/2 text-gray-300" size={20} />
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={settings.evm_platform_address}
|
||||
onChange={(e) => setSettings({ ...settings, evm_platform_address: e.target.value })}
|
||||
placeholder="0x..."
|
||||
className="w-full pl-14 pr-6 py-5 bg-gray-50 border-2 border-transparent focus:border-blue-500 focus:bg-white rounded-[24px] outline-none transition-all font-bold text-gray-900 font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Security Section */}
|
||||
<section className="bg-white p-10 rounded-[40px] border border-gray-100 shadow-sm space-y-8">
|
||||
<div className="flex items-center gap-4 border-b border-gray-50 pb-6">
|
||||
<div className="w-12 h-12 bg-emerald-50 rounded-2xl flex items-center justify-center text-emerald-600">
|
||||
<ShieldCheck size={24} />
|
||||
</div>
|
||||
<h2 className="text-xl font-black text-gray-900 uppercase tracking-tight">Güvenlik</h2>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between p-6 bg-gray-50 rounded-[32px]">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-black text-gray-900">İki Faktörlü Doğrulama</p>
|
||||
<p className="text-[10px] text-gray-400 font-bold uppercase">Hesabınıza ekstra bir güvenlik katmanı ekleyin</p>
|
||||
</div>
|
||||
<div className="w-12 h-6 bg-blue-500 rounded-full relative cursor-pointer">
|
||||
<div className="absolute right-1 top-1 w-4 h-4 bg-white rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between p-6 bg-gray-50 rounded-[32px]">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-black text-gray-900">API Erişimi</p>
|
||||
<p className="text-[10px] text-gray-400 font-bold uppercase">Harici uygulamalar için anahtar yönetimi</p>
|
||||
</div>
|
||||
<button className="text-blue-600 text-[10px] font-black uppercase tracking-widest hover:underline">Anahtarları Düzenle</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{/* Right Column: Notifications & Danger Zone */}
|
||||
<div className="space-y-8">
|
||||
<section className="bg-white p-8 rounded-[40px] border border-gray-100 shadow-sm space-y-6">
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
<div className="w-10 h-10 bg-orange-50 rounded-xl flex items-center justify-center text-orange-600">
|
||||
<Bell size={20} />
|
||||
</div>
|
||||
<h2 className="text-lg font-black text-gray-900 uppercase tracking-tight">Bildirimler</h2>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
{['Yeni Satışlar', 'Müşteri Mesajları', 'Sistem Güncellemeleri'].map((item) => (
|
||||
<label key={item} className="flex items-center gap-4 cursor-pointer group">
|
||||
<input type="checkbox" defaultChecked className="w-5 h-5 rounded-lg border-2 border-gray-100 text-blue-600 focus:ring-blue-500" />
|
||||
<span className="text-sm font-bold text-gray-500 group-hover:text-gray-900 transition">{item}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="bg-red-50 p-8 rounded-[40px] border border-red-100 space-y-6">
|
||||
<div className="flex items-center gap-4 text-red-600">
|
||||
<Trash2 size={20} />
|
||||
<h2 className="text-lg font-black uppercase tracking-tight">Tehlikeli Bölge</h2>
|
||||
</div>
|
||||
<p className="text-xs font-bold text-red-400 leading-relaxed uppercase tracking-wider">
|
||||
Mağaza verilerini kalıcı olarak silmek veya hesabı kapatmak için bu bölümü kullanın. Bu işlem geri alınamaz.
|
||||
<div className="p-6 bg-blue-50 rounded-3xl border border-blue-100 flex gap-4 items-start">
|
||||
<AlertCircle className="text-blue-600 mt-1 shrink-0" size={20} />
|
||||
<p className="text-xs text-blue-600 font-medium leading-relaxed">
|
||||
Bu adresler, merchant'ların yaptığı tahsilatların (komisyonunuz kesildikten sonra kalan kısmın değil, platforma ait ana kesintinin veya tüm fonların) toplanacağı ana havuzunuzdur. Lütfen adreslerin doğruluğunu iki kez kontrol edin.
|
||||
</p>
|
||||
<button className="w-full py-4 bg-red-600 text-white rounded-2xl font-black text-sm hover:bg-red-700 transition shadow-lg shadow-red-100">
|
||||
Mağazayı Devre Dışı Bırak
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer Meta */}
|
||||
<div className="pt-12 text-center">
|
||||
<p className="text-[10px] text-gray-300 font-black uppercase tracking-[0.4em]">v1.0.4 Platinum Enterprise Edition</p>
|
||||
</div>
|
||||
{/* Info Card: Gas Tank Secrets */}
|
||||
<div className="bg-gray-900 p-10 rounded-[48px] shadow-2xl space-y-8 relative overflow-hidden group">
|
||||
<div className="absolute top-0 right-0 p-10 opacity-5 group-hover:scale-110 transition-transform">
|
||||
<Key size={120} className="text-white" />
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 relative">
|
||||
<div className="w-12 h-12 bg-white/10 rounded-2xl flex items-center justify-center text-white">
|
||||
<Zap size={24} />
|
||||
</div>
|
||||
<h3 className="text-2xl font-black text-white italic">Kritik Güvenlik: Gas Tank Private Key</h3>
|
||||
<p className="text-gray-400 font-medium leading-relaxed max-w-2xl">
|
||||
Süpürme (Sweep) işlemlerinin blokzinciri ücretlerini ödeyen "Gaz Tankı" cüzdanınızın Private Key'i, en yüksek güvenlik için veritabanında değil, sunucu tarafında <strong>Coolify Environment Variables</strong> (`CRYPTO_GAS_TANK_KEY`) olarak saklanmalıdır.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 p-6 bg-white/5 rounded-3xl border border-white/10 items-center">
|
||||
<div className="w-2 h-2 rounded-full bg-emerald-500"></div>
|
||||
<span className="text-[10px] font-black text-gray-500 uppercase tracking-widest">Coolify Panelden Yönetildi</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Status and Action */}
|
||||
{message.text && (
|
||||
<div className={`p-6 rounded-[30px] border flex items-center gap-4 animate-in slide-in-from-top-2 duration-300 ${
|
||||
message.type === 'success' ? 'bg-emerald-50 border-emerald-100 text-emerald-700' : 'bg-red-50 border-red-100 text-red-700'
|
||||
}`}>
|
||||
{message.type === 'success' ? <ShieldCheck size={20} /> : <AlertCircle size={20} />}
|
||||
<span className="text-xs font-black uppercase tracking-widest italic">{message.text}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end gap-6 pt-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={fetchSettings}
|
||||
className="p-5 text-gray-400 hover:text-gray-900 transition-colors"
|
||||
title="Yenile"
|
||||
>
|
||||
<RefreshCcw size={24} />
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSaving || isLoading}
|
||||
className="px-14 py-6 bg-blue-600 text-white rounded-[28px] font-black text-sm uppercase tracking-[0.2em] hover:bg-blue-700 transition shadow-2xl shadow-blue-200 disabled:opacity-50 flex items-center gap-3 italic"
|
||||
>
|
||||
{isSaving ? 'Kaydediliyor...' : 'Konfigurasyonu Güncelle'}
|
||||
<Save size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
54
app/api/admin/settings/route.ts
Normal file
54
app/api/admin/settings/route.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
// Ensure table exists (Safe initialization)
|
||||
await db.query(`
|
||||
CREATE TABLE IF NOT EXISTS system_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)
|
||||
`);
|
||||
|
||||
const result = await db.query('SELECT * FROM system_settings');
|
||||
|
||||
// Convert to key-value object
|
||||
const settings: Record<string, string> = {};
|
||||
result.rows.forEach(row => {
|
||||
settings[row.key] = row.value;
|
||||
});
|
||||
|
||||
// Fill defaults if empty
|
||||
if (!settings.sol_platform_address) settings.sol_platform_address = process.env.SOL_PLATFORM_ADDRESS || '';
|
||||
if (!settings.evm_platform_address) settings.evm_platform_address = process.env.EVM_PLATFORM_ADDRESS || '';
|
||||
|
||||
return NextResponse.json(settings);
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: err.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { sol_platform_address, evm_platform_address } = body;
|
||||
|
||||
const queries = [
|
||||
{ key: 'sol_platform_address', value: sol_platform_address },
|
||||
{ key: 'evm_platform_address', value: evm_platform_address }
|
||||
];
|
||||
|
||||
for (const q of queries) {
|
||||
await db.query(
|
||||
'INSERT INTO system_settings (key, value, updated_at) VALUES ($1, $2, NOW()) ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = NOW()',
|
||||
[q.key, q.value]
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (err: any) {
|
||||
return NextResponse.json({ error: err.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -45,10 +45,18 @@ export async function POST(request: Request) {
|
||||
return NextResponse.json({ success: false, error: `No temporary wallet found for ${walletType}` }, { status: 500 });
|
||||
}
|
||||
|
||||
// 3. Define Platform Address (In production, load from env/settings)
|
||||
const platformAddress = selectedNetwork === 'SOLANA'
|
||||
? process.env.SOL_PLATFORM_ADDRESS || "5pLH1tqZhx8p8WpZ18yr28N42KXB3FXVPzZ9ceCtpBVe"
|
||||
: process.env.EVM_PLATFORM_ADDRESS || "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
|
||||
// 3. Define Platform Address (Fetch from dynamic settings)
|
||||
const platformAddresses = await (async () => {
|
||||
const result = await db.query('SELECT key, value FROM system_settings WHERE key IN (\'sol_platform_address\', \'evm_platform_address\')');
|
||||
const map: Record<string, string> = {};
|
||||
result.rows.forEach(r => map[r.key] = r.value);
|
||||
return {
|
||||
sol: map.sol_platform_address || process.env.SOL_PLATFORM_ADDRESS || "5pLH1tqZhx8p8WpZ18yr28N42KXB3FXVPzZ9ceCtpBVe",
|
||||
evm: map.evm_platform_address || process.env.EVM_PLATFORM_ADDRESS || "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
|
||||
};
|
||||
})();
|
||||
|
||||
const platformAddress = selectedNetwork === 'SOLANA' ? platformAddresses.sol : platformAddresses.evm;
|
||||
|
||||
// 4. Define Merchant Address (Fetch from transaction's merchant)
|
||||
const merchantResult = await db.query('SELECT * FROM merchants WHERE id = $1', [transaction.merchant_id]);
|
||||
|
||||
19
lib/settings.ts
Normal file
19
lib/settings.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { db } from './db';
|
||||
|
||||
export async function getSystemSetting(key: string, defaultValue: string = ''): Promise<string> {
|
||||
try {
|
||||
const result = await db.query('SELECT value FROM system_settings WHERE key = $1', [key]);
|
||||
if (result.rows.length > 0) {
|
||||
return result.rows[0].value;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[Settings] Failed to fetch key ${key}:`, err);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
export async function getPlatformAddresses() {
|
||||
const sol = await getSystemSetting('sol_platform_address', process.env.SOL_PLATFORM_ADDRESS || '');
|
||||
const evm = await getSystemSetting('evm_platform_address', process.env.EVM_PLATFORM_ADDRESS || '');
|
||||
return { sol, evm };
|
||||
}
|
||||
Reference in New Issue
Block a user