Spaces:
Running
Running
| import React, { useState } from 'react'; | |
| import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; | |
| import { Badge } from '@/components/ui/badge'; | |
| import { Button } from '@/components/ui/button'; | |
| import { Input } from '@/components/ui/input'; | |
| import { | |
| Table, | |
| TableBody, | |
| TableCell, | |
| TableHead, | |
| TableHeader, | |
| TableRow, | |
| } from '@/components/ui/table'; | |
| import { | |
| Activity, | |
| Download, | |
| Search, | |
| TrendingUp, | |
| TrendingDown, | |
| ExternalLink, | |
| Clock | |
| } from 'lucide-react'; | |
| // Mock trade data | |
| const mockTrades = [ | |
| { | |
| id: '1', | |
| timestamp: new Date('2024-01-07T14:30:00Z'), | |
| targetWallet: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM', | |
| pair: 'SOL/USDC', | |
| type: 'BUY', | |
| amount: 2.5, | |
| price: 98.45, | |
| pnl: 156.80, | |
| status: 'completed', | |
| signature: '2uJYB9jKzZi8YMFQWyJ8UvBdJLnVcP9q3HrXj6K4T7N8L9R1' | |
| }, | |
| { | |
| id: '2', | |
| timestamp: new Date('2024-01-07T13:15:00Z'), | |
| targetWallet: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM', | |
| pair: 'RAY/SOL', | |
| type: 'SELL', | |
| amount: 150, | |
| price: 0.045, | |
| pnl: -23.40, | |
| status: 'completed', | |
| signature: '3vKYC0jLzZi9YNGRXyK9VvCeKMoWdQ0r4IsYk7L5U8O9M0S2' | |
| }, | |
| { | |
| id: '3', | |
| timestamp: new Date('2024-01-07T12:45:00Z'), | |
| targetWallet: 'BQWf2RqVVJ8ZfP4HuRr6nF2jN8Y5K3L9R1T7U4W6X2B5M9S8', | |
| pair: 'ORCA/USDC', | |
| type: 'BUY', | |
| amount: 85.2, | |
| price: 1.23, | |
| pnl: 45.60, | |
| status: 'completed', | |
| signature: '4wLYD1kMzZj0YOHSYyL0WwDfLNpXeR1s5JtZl8M6V9P0N1T3' | |
| }, | |
| { | |
| id: '4', | |
| timestamp: new Date('2024-01-07T11:20:00Z'), | |
| targetWallet: 'BQWf2RqVVJ8ZfP4HuRr6nF2jN8Y5K3L9R1T7U4W6X2B5M9S8', | |
| pair: 'SOL/USDC', | |
| type: 'BUY', | |
| amount: 1.8, | |
| price: 97.20, | |
| pnl: 0, | |
| status: 'pending', | |
| signature: '5xMYE2lNzZk1YPITZyM1XxEgMOqYfS2t6KuAm9N7W0Q1O2U4' | |
| }, | |
| { | |
| id: '5', | |
| timestamp: new Date('2024-01-07T10:05:00Z'), | |
| targetWallet: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM', | |
| pair: 'BONK/SOL', | |
| type: 'SELL', | |
| amount: 1000000, | |
| price: 0.0000089, | |
| pnl: 78.90, | |
| status: 'completed', | |
| signature: '6yNYF3mOzZl2YQJUAyN2YyFhNPrZgT3u7LvBn0O8X1R2P3V5' | |
| } | |
| ]; | |
| export const TradeLog: React.FC = () => { | |
| const [searchTerm, setSearchTerm] = useState(''); | |
| const filteredTrades = mockTrades.filter(trade => | |
| trade.pair.toLowerCase().includes(searchTerm.toLowerCase()) || | |
| trade.targetWallet.toLowerCase().includes(searchTerm.toLowerCase()) || | |
| trade.type.toLowerCase().includes(searchTerm.toLowerCase()) | |
| ); | |
| const handleExportTrades = () => { | |
| // Create CSV content | |
| const csvContent = [ | |
| ['Timestamp', 'Target Wallet', 'Pair', 'Type', 'Amount', 'Price', 'P&L', 'Status', 'Signature'], | |
| ...filteredTrades.map(trade => [ | |
| trade.timestamp.toISOString(), | |
| trade.targetWallet, | |
| trade.pair, | |
| trade.type, | |
| trade.amount.toString(), | |
| trade.price.toString(), | |
| trade.pnl.toString(), | |
| trade.status, | |
| trade.signature | |
| ]) | |
| ].map(row => row.join(',')).join('\n'); | |
| // Create and download file | |
| const blob = new Blob([csvContent], { type: 'text/csv' }); | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `trade-log-${new Date().toISOString().split('T')[0]}.csv`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| window.URL.revokeObjectURL(url); | |
| }; | |
| const getStatusBadge = (status: string) => { | |
| switch (status) { | |
| case 'completed': | |
| return <Badge variant="default" className="bg-profit text-profit-foreground">Completed</Badge>; | |
| case 'pending': | |
| return <Badge variant="secondary" className="bg-warning text-warning-foreground">Pending</Badge>; | |
| case 'failed': | |
| return <Badge variant="destructive">Failed</Badge>; | |
| default: | |
| return <Badge variant="outline">{status}</Badge>; | |
| } | |
| }; | |
| const getPnLDisplay = (pnl: number, status: string) => { | |
| if (status === 'pending') { | |
| return <span className="text-muted-foreground">--</span>; | |
| } | |
| return ( | |
| <span className={pnl >= 0 ? 'text-profit' : 'text-loss'}> | |
| {pnl >= 0 ? '+' : ''}${pnl.toFixed(2)} | |
| </span> | |
| ); | |
| }; | |
| return ( | |
| <Card className="bg-gradient-card border-border/50"> | |
| <CardHeader> | |
| <div className="flex items-center justify-between"> | |
| <CardTitle className="flex items-center gap-2"> | |
| <Activity className="h-5 w-5 text-primary" /> | |
| Trade Log | |
| </CardTitle> | |
| <div className="flex items-center gap-2"> | |
| <div className="relative"> | |
| <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" /> | |
| <Input | |
| placeholder="Search trades..." | |
| value={searchTerm} | |
| onChange={(e) => setSearchTerm(e.target.value)} | |
| className="pl-8 w-64" | |
| /> | |
| </div> | |
| <Button onClick={handleExportTrades} variant="outline" className="gap-2"> | |
| <Download className="h-4 w-4" /> | |
| Export CSV | |
| </Button> | |
| </div> | |
| </div> | |
| </CardHeader> | |
| <CardContent> | |
| <div className="overflow-x-auto"> | |
| <Table> | |
| <TableHeader> | |
| <TableRow> | |
| <TableHead>Time</TableHead> | |
| <TableHead>Target Wallet</TableHead> | |
| <TableHead>Pair</TableHead> | |
| <TableHead>Type</TableHead> | |
| <TableHead>Amount</TableHead> | |
| <TableHead>Price</TableHead> | |
| <TableHead>P&L</TableHead> | |
| <TableHead>Status</TableHead> | |
| <TableHead>Action</TableHead> | |
| </TableRow> | |
| </TableHeader> | |
| <TableBody> | |
| {filteredTrades.map((trade, index) => ( | |
| <TableRow | |
| key={trade.id} | |
| className="animate-fade-in" | |
| style={{ animationDelay: `${index * 0.05}s` }} | |
| > | |
| <TableCell> | |
| <div className="flex items-center gap-1 text-sm"> | |
| <Clock className="h-3 w-3 text-muted-foreground" /> | |
| {trade.timestamp.toLocaleTimeString('en-US', { | |
| hour: '2-digit', | |
| minute: '2-digit' | |
| })} | |
| </div> | |
| <div className="text-xs text-muted-foreground"> | |
| {trade.timestamp.toLocaleDateString('en-US', { | |
| month: 'short', | |
| day: 'numeric' | |
| })} | |
| </div> | |
| </TableCell> | |
| <TableCell> | |
| <div className="font-mono text-xs"> | |
| {trade.targetWallet.slice(0, 6)}...{trade.targetWallet.slice(-4)} | |
| </div> | |
| </TableCell> | |
| <TableCell> | |
| <span className="font-medium">{trade.pair}</span> | |
| </TableCell> | |
| <TableCell> | |
| <Badge | |
| variant={trade.type === 'BUY' ? 'default' : 'secondary'} | |
| className={trade.type === 'BUY' ? 'bg-profit text-profit-foreground' : 'bg-loss text-loss-foreground'} | |
| > | |
| {trade.type === 'BUY' ? ( | |
| <TrendingUp className="h-3 w-3 mr-1" /> | |
| ) : ( | |
| <TrendingDown className="h-3 w-3 mr-1" /> | |
| )} | |
| {trade.type} | |
| </Badge> | |
| </TableCell> | |
| <TableCell> | |
| <span className="font-mono text-sm">{trade.amount.toLocaleString()}</span> | |
| </TableCell> | |
| <TableCell> | |
| <span className="font-mono text-sm">${trade.price.toFixed(4)}</span> | |
| </TableCell> | |
| <TableCell className="font-bold"> | |
| {getPnLDisplay(trade.pnl, trade.status)} | |
| </TableCell> | |
| <TableCell> | |
| {getStatusBadge(trade.status)} | |
| </TableCell> | |
| <TableCell> | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| className="gap-1" | |
| onClick={() => window.open(`https://explorer.solana.com/tx/${trade.signature}?cluster=devnet`, '_blank')} | |
| > | |
| <ExternalLink className="h-3 w-3" /> | |
| View | |
| </Button> | |
| </TableCell> | |
| </TableRow> | |
| ))} | |
| </TableBody> | |
| </Table> | |
| </div> | |
| {filteredTrades.length === 0 && ( | |
| <div className="text-center py-8 text-muted-foreground"> | |
| <Activity className="h-12 w-12 mx-auto mb-3 opacity-50" /> | |
| <p>No trades found</p> | |
| <p className="text-sm"> | |
| {searchTerm ? 'Try adjusting your search terms' : 'Start trading to see your trade history'} | |
| </p> | |
| </div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| ); | |
| }; |