- Fixed layout conflicts by removing minimal layout.tsx in favor of complete layout.js - Restored original AI Analysis page with full TradingView integration - Connected enhanced screenshot API to real TradingView automation service - Fixed screenshot gallery to handle both string and object formats - Added image serving API route for screenshot display - Resolved hydration mismatch issues with suppressHydrationWarning - All navigation pages working (Analysis, Trading, Automation, Settings) - TradingView automation successfully capturing screenshots from AI and DIY layouts - Docker Compose v2 compatibility ensured Working features: - Homepage with hero section and status cards - Navigation menu with Trading Bot branding - Real TradingView screenshot capture - AI-powered chart analysis - Multi-layout support (AI + DIY module) - Screenshot gallery with image serving - API endpoints for balance, status, screenshots, trading
182 lines
6.3 KiB
TypeScript
182 lines
6.3 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
|
|
interface TokenPrice {
|
|
symbol: string;
|
|
price: number;
|
|
change24h: number;
|
|
volume24h: number;
|
|
marketCap?: number;
|
|
}
|
|
|
|
interface TradingBalance {
|
|
totalValue: number;
|
|
availableBalance: number;
|
|
positions: TokenPrice[];
|
|
}
|
|
|
|
export default function BitqueryDashboard() {
|
|
const [balance, setBalance] = useState<TradingBalance | null>(null);
|
|
const [prices, setPrices] = useState<TokenPrice[]>([]);
|
|
const [status, setStatus] = useState<any>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [tradingSymbol, setTradingSymbol] = useState('SOL');
|
|
const [tradeAmount, setTradeAmount] = useState('1');
|
|
const [tradeSide, setTradeSide] = useState<'BUY' | 'SELL'>('BUY');
|
|
const [tradeLoading, setTradeLoading] = useState(false);
|
|
const [tradeResult, setTradeResult] = useState<any>(null);
|
|
|
|
useEffect(() => {
|
|
fetchData();
|
|
const interval = setInterval(fetchData, 30000); // Refresh every 30 seconds
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
const fetchData = async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
// Fetch balance
|
|
const balanceResponse = await fetch('/api/balance');
|
|
const balanceData = await balanceResponse.json();
|
|
if (balanceData.success) {
|
|
setBalance(balanceData.data);
|
|
}
|
|
|
|
// Fetch prices
|
|
const pricesResponse = await fetch('/api/prices');
|
|
const pricesData = await pricesResponse.json();
|
|
if (pricesData.success) {
|
|
setPrices(pricesData.data);
|
|
}
|
|
|
|
// Fetch status
|
|
const statusResponse = await fetch('/api/status');
|
|
const statusData = await statusResponse.json();
|
|
if (statusData.success) {
|
|
setStatus(statusData.data);
|
|
}
|
|
|
|
} catch (err: any) {
|
|
setError(err.message || 'Failed to fetch data');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
if (loading && !balance) {
|
|
return (
|
|
<div className="min-h-screen bg-gray-900 text-white p-8">
|
|
<div className="max-w-6xl mx-auto">
|
|
<h1 className="text-3xl font-bold mb-8">Bitquery Trading Dashboard</h1>
|
|
<div className="text-center">Loading...</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="min-h-screen bg-gray-900 text-white p-8">
|
|
<div className="max-w-6xl mx-auto">
|
|
<h1 className="text-3xl font-bold mb-8">Bitquery Trading Dashboard</h1>
|
|
<div className="bg-red-900 border border-red-700 rounded-lg p-4">
|
|
<div className="text-red-200">Error: {error}</div>
|
|
<button
|
|
onClick={fetchData}
|
|
className="mt-2 px-4 py-2 bg-red-700 hover:bg-red-600 rounded"
|
|
>
|
|
Retry
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-900 text-white p-8">
|
|
<div className="max-w-6xl mx-auto">
|
|
<h1 className="text-3xl font-bold mb-8">Bitquery Trading Dashboard</h1>
|
|
|
|
{/* Service Status */}
|
|
{status && (
|
|
<div className="mb-8 bg-gray-800 rounded-lg p-6">
|
|
<h2 className="text-xl font-semibold mb-4">Service Status</h2>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="flex items-center">
|
|
<div className={`w-3 h-3 rounded-full mr-2 ${status.bitquery?.connected ? 'bg-green-500' : 'bg-red-500'}`}></div>
|
|
<span>Bitquery: {status.bitquery?.connected ? 'Connected' : 'Disconnected'}</span>
|
|
</div>
|
|
<div className="flex items-center">
|
|
<div className={`w-3 h-3 rounded-full mr-2 ${status.bitquery?.apiKey ? 'bg-green-500' : 'bg-red-500'}`}></div>
|
|
<span>API Key: {status.bitquery?.apiKey ? 'Configured' : 'Missing'}</span>
|
|
</div>
|
|
</div>
|
|
{status.bitquery?.error && (
|
|
<div className="mt-2 text-red-400">Error: {status.bitquery.error}</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Balance Overview */}
|
|
{balance && (
|
|
<div className="mb-8 bg-gray-800 rounded-lg p-6">
|
|
<h2 className="text-xl font-semibold mb-4">Portfolio Balance</h2>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<div className="text-sm text-gray-400">Total Value</div>
|
|
<div className="text-2xl font-bold">${balance.totalValue.toFixed(2)}</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-sm text-gray-400">Available Balance</div>
|
|
<div className="text-2xl font-bold">${balance.availableBalance.toFixed(2)}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Token Prices */}
|
|
<div className="mb-8 bg-gray-800 rounded-lg p-6">
|
|
<h2 className="text-xl font-semibold mb-4">Token Prices</h2>
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
{prices.map((token) => (
|
|
<div key={token.symbol} className="bg-gray-700 rounded-lg p-4">
|
|
<div className="flex justify-between items-center mb-2">
|
|
<span className="font-semibold">{token.symbol}</span>
|
|
<span className={`text-sm ${token.change24h >= 0 ? 'text-green-400' : 'text-red-400'}`}>
|
|
{token.change24h >= 0 ? '+' : ''}{token.change24h.toFixed(2)}%
|
|
</span>
|
|
</div>
|
|
<div className="text-2xl font-bold">${token.price.toFixed(2)}</div>
|
|
<div className="text-sm text-gray-400">
|
|
Vol: ${token.volume24h.toLocaleString()}
|
|
</div>
|
|
{token.marketCap && (
|
|
<div className="text-sm text-gray-400">
|
|
Cap: ${(token.marketCap / 1e9).toFixed(2)}B
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Refresh Button */}
|
|
<div className="text-center">
|
|
<button
|
|
onClick={fetchData}
|
|
disabled={loading}
|
|
className="px-6 py-3 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-600 rounded-lg font-semibold"
|
|
>
|
|
{loading ? 'Refreshing...' : 'Refresh Data'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|