Skip to content

Commit 2d1e1b6

Browse files
authored
Merge pull request #80 from openSVM/copilot/fix-66
Redesign analytics dashboard with protocol-specific trading metrics, ASCII theme, ASCII vertical bar chart with improved spacing, and responsive mobile-first design
2 parents 2e6a31c + 2878b03 commit 2d1e1b6

17 files changed

+3687
-3
lines changed

package-lock.json

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,18 @@
4040
"dependencies": {
4141
"@coral-xyz/anchor": "0.31.1",
4242
"@coral-xyz/anchor-cli": "^0.31.2",
43+
"@getpara/web-sdk": "^1.11.0",
44+
"@noble/curves": "^1.3.0",
45+
"@noble/hashes": "^1.3.3",
4346
"@project-serum/anchor": "^0.26.0",
4447
"@solana/spl-token": "^0.4.13",
4548
"@solana/web3.js": "1.98.2",
46-
"@getpara/web-sdk": "^1.11.0",
4749
"@swig-wallet/classic": "0.2.0-beta.4",
4850
"@swig-wallet/coder": "0.2.0-beta.2",
49-
"@noble/curves": "^1.3.0",
50-
"@noble/hashes": "^1.3.3",
5151
"ajv": "^8.17.1",
5252
"browserify-zlib": "^0.2.0",
5353
"buffer": "^6.0.3",
54+
"chart.js": "^4.5.0",
5455
"crypto-browserify": "^3.12.1",
5556
"fastestsmallesttextencoderdecoder": "^1.0.22",
5657
"https-browserify": "^1.0.0",
@@ -59,6 +60,7 @@
5960
"next-i18next": "^15.4.2",
6061
"process": "^0.11.10",
6162
"react": "19.1.0",
63+
"react-chartjs-2": "^5.3.0",
6264
"react-dom": "19.1.0",
6365
"react-i18next": "^15.5.2",
6466
"stream-browserify": "^3.0.0",
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import React, { useState, useEffect, useContext } from 'react';
2+
import { AppContext } from '@/contexts/AppContext';
3+
import { useSwigWallet } from '@/contexts/SwigWalletProvider';
4+
import OverviewPanel from '@/components/analytics/OverviewPanel';
5+
import RecentTrades from '@/components/analytics/RecentTrades';
6+
import VolumePerDayChart from '@/components/analytics/VolumePerDayChart';
7+
import TopTraders from '@/components/analytics/TopTraders';
8+
9+
export default function AnalyticsDashboard() {
10+
const { network, selectedNetwork, networks } = useContext(AppContext);
11+
const { connected, publicKey } = useSwigWallet();
12+
const [timeframe, setTimeframe] = useState('24h');
13+
const [refreshInterval, setRefreshInterval] = useState(null);
14+
15+
// Real-time protocol data states
16+
const [protocolOverview, setProtocolOverview] = useState({
17+
totalTrades: 0,
18+
protocolVolume: 0,
19+
totalFees: 0,
20+
completionRate: 0,
21+
tradesChange: 0,
22+
volumeChange: 0,
23+
feesChange: 0,
24+
completionChange: 0
25+
});
26+
27+
const [recentTrades, setRecentTrades] = useState([]);
28+
const [volumeData, setVolumeData] = useState([]);
29+
const [topTradersData, setTopTradersData] = useState([]);
30+
31+
// Initialize real-time data fetching
32+
useEffect(() => {
33+
// Initial data fetch
34+
fetchAllProtocolData();
35+
36+
// Set up real-time updates every 5 seconds
37+
const interval = setInterval(fetchAllProtocolData, 5000);
38+
setRefreshInterval(interval);
39+
40+
return () => {
41+
if (interval) {
42+
clearInterval(interval);
43+
}
44+
};
45+
}, [selectedNetwork, timeframe]);
46+
47+
const fetchAllProtocolData = async () => {
48+
try {
49+
// Simulate API calls to fetch real-time protocol data
50+
await Promise.all([
51+
fetchProtocolOverview(),
52+
fetchRecentTradesData(),
53+
fetchVolumeData(),
54+
fetchTopTradersData()
55+
]);
56+
} catch (error) {
57+
console.error('Error fetching protocol analytics data:', error);
58+
}
59+
};
60+
61+
const fetchProtocolOverview = async () => {
62+
// Simulate fetching protocol overview metrics
63+
const mockData = {
64+
totalTrades: Math.floor(Math.random() * 5000) + 1000, // 1000-6000 trades
65+
protocolVolume: Math.random() * 50000 + 10000, // 10K-60K SOL
66+
totalFees: Math.random() * 50000 + 5000, // $5K-$55K in fees
67+
completionRate: 85 + (Math.random() * 10), // 85-95%
68+
tradesChange: Math.random() * 20 - 5, // -5% to +15%
69+
volumeChange: Math.random() * 30 - 10, // -10% to +20%
70+
feesChange: Math.random() * 25 - 5, // -5% to +20%
71+
completionChange: Math.random() * 5 - 2 // -2% to +3%
72+
};
73+
setProtocolOverview(mockData);
74+
};
75+
76+
const fetchRecentTradesData = async () => {
77+
// Simulate fetching recent protocol trades (last 100)
78+
const mockTrades = Array.from({ length: 100 }, (_, i) => {
79+
const tradeId = `T${Date.now().toString().slice(-6)}_${i.toString().padStart(3, '0')}`;
80+
const types = ['buy', 'sell'];
81+
const statuses = ['completed', 'in_progress', 'cancelled', 'disputed'];
82+
const currencies = ['USD', 'EUR', 'GBP'];
83+
84+
const solAmount = Math.random() * 10 + 0.1; // 0.1-10 SOL
85+
const rate = 130 + (Math.random() * 40); // $130-170 per SOL
86+
const fiatAmount = solAmount * rate;
87+
88+
return {
89+
tradeId,
90+
type: types[Math.floor(Math.random() * types.length)],
91+
status: statuses[Math.floor(Math.random() * statuses.length)],
92+
buyer: `${Math.random().toString(36).substring(2, 15)}`,
93+
seller: `${Math.random().toString(36).substring(2, 15)}`,
94+
solAmount,
95+
fiatAmount,
96+
currency: currencies[Math.floor(Math.random() * currencies.length)],
97+
rate,
98+
timestamp: new Date(Date.now() - Math.random() * 86400000 * 7), // Last 7 days
99+
completionTime: Math.random() > 0.7 ? `${Math.floor(Math.random() * 30 + 5)}min` : null,
100+
protocolFee: solAmount * 0.005 // 0.5% protocol fee
101+
};
102+
});
103+
104+
// Sort by timestamp, newest first
105+
mockTrades.sort((a, b) => b.timestamp - a.timestamp);
106+
setRecentTrades(mockTrades);
107+
};
108+
109+
const fetchVolumeData = async () => {
110+
// Simulate fetching volume data based on timeframe
111+
let dataPointsCount;
112+
let timeIncrement;
113+
114+
switch (timeframe) {
115+
case '1h':
116+
dataPointsCount = 60; // 60 minutes
117+
timeIncrement = 60 * 1000; // 1 minute
118+
break;
119+
case '24h':
120+
dataPointsCount = 24; // 24 hours
121+
timeIncrement = 60 * 60 * 1000; // 1 hour
122+
break;
123+
case '7d':
124+
dataPointsCount = 7; // 7 days
125+
timeIncrement = 24 * 60 * 60 * 1000; // 1 day
126+
break;
127+
case '30d':
128+
dataPointsCount = 30; // 30 days
129+
timeIncrement = 24 * 60 * 60 * 1000; // 1 day
130+
break;
131+
default:
132+
dataPointsCount = 24;
133+
timeIncrement = 60 * 60 * 1000;
134+
}
135+
136+
const now = new Date();
137+
const volumePoints = Array.from({ length: dataPointsCount }, (_, i) => {
138+
const time = new Date(now.getTime() - (dataPointsCount - 1 - i) * timeIncrement);
139+
const baseVolume = 1000 + Math.random() * 5000; // Base volume
140+
const volume = baseVolume + Math.sin(i / 5) * 1000; // Add some wave pattern
141+
142+
return {
143+
time: time.toISOString(),
144+
volume: Math.max(0, volume) // Ensure non-negative
145+
};
146+
});
147+
148+
setVolumeData(volumePoints);
149+
};
150+
151+
const fetchTopTradersData = async () => {
152+
// Simulate fetching top 100 traders data
153+
const mockTraders = Array.from({ length: 100 }, (_, i) => {
154+
const baseAddress = Math.random().toString(36).substring(2, 15);
155+
const address = `${baseAddress}...${Math.random().toString(36).substring(2, 6)}`;
156+
157+
const tradeCount = Math.floor(Math.random() * 500) + 10; // 10-510 trades
158+
const volume = Math.random() * 100000 + 1000; // 1K-101K SOL
159+
const pnl = (Math.random() - 0.3) * 50000; // -15K to +35K (bias towards positive)
160+
const successRate = 60 + Math.random() * 35; // 60-95%
161+
162+
return {
163+
address,
164+
tradeCount,
165+
volume,
166+
pnl,
167+
successRate,
168+
verified: Math.random() > 0.8 // 20% are verified
169+
};
170+
});
171+
172+
setTopTradersData(mockTraders);
173+
};
174+
175+
const timeframeOptions = [
176+
{ value: '1h', label: '1H' },
177+
{ value: '24h', label: '24H' },
178+
{ value: '7d', label: '7D' },
179+
{ value: '30d', label: '30D' }
180+
];
181+
182+
return (
183+
<div className="analytics-dashboard">
184+
<div className="analytics-header">
185+
<div className="header-content">
186+
<div className="title-section">
187+
<h1 className="analytics-title">
188+
Protocol Analytics Dashboard
189+
</h1>
190+
<p className="analytics-subtitle">
191+
Monitor svmp2p trading performance and user metrics on {network.name}
192+
</p>
193+
</div>
194+
195+
<div className="header-controls">
196+
<div className="timeframe-selector">
197+
{timeframeOptions.map(option => (
198+
<button
199+
key={option.value}
200+
className={`timeframe-button ${timeframe === option.value ? 'active' : ''}`}
201+
onClick={() => setTimeframe(option.value)}
202+
>
203+
{option.label}
204+
</button>
205+
))}
206+
</div>
207+
208+
<div className="connection-status">
209+
{connected ? (
210+
<span className="status-connected">
211+
[ONLINE] {network.name}
212+
</span>
213+
) : (
214+
<span className="status-disconnected">
215+
[OFFLINE] WALLET NOT CONNECTED
216+
</span>
217+
)}
218+
</div>
219+
</div>
220+
</div>
221+
</div>
222+
223+
<div className="analytics-content">
224+
{/* Protocol Overview Panel - KPI Summary */}
225+
<OverviewPanel
226+
data={protocolOverview}
227+
network={network}
228+
timeframe={timeframe}
229+
/>
230+
231+
<div className="analytics-grid">
232+
{/* Left Column - Volume Chart and Top Traders */}
233+
<div className="analytics-column-left">
234+
{/* Volume Per Day Chart */}
235+
<VolumePerDayChart
236+
data={volumeData}
237+
network={network}
238+
timeframe={timeframe}
239+
/>
240+
241+
{/* Top Traders Rankings */}
242+
<TopTraders
243+
tradersData={topTradersData}
244+
/>
245+
</div>
246+
247+
{/* Right Column - Recent Trades Feed */}
248+
<div className="analytics-column-right">
249+
<RecentTrades
250+
trades={recentTrades}
251+
network={network}
252+
/>
253+
</div>
254+
</div>
255+
</div>
256+
</div>
257+
);
258+
}

src/components/Layout.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export default function Layout({ children, title = 'OpenSVM P2P Exchange' }) {
7878
const topNavItems = [
7979
{ key: 'buy', label: 'BUY', icon: 'B' },
8080
{ key: 'sell', label: 'SELL', icon: 'S' },
81+
{ key: 'analytics', label: 'ANALYTICS', icon: '📊' },
8182
{ key: 'help', label: 'HELP', icon: '?' },
8283
];
8384

0 commit comments

Comments
 (0)