Files
trading_bot_v3/components/TradeModal.tsx
mindesbunister f0845d0fd1 restore: Complete Jupiter-style trading interface from git history
Fully restored complete trading page with all components:
- Complete Jupiter Perps-style UI with coin selection dropdown
- Real wallet balance integration and live trading capabilities
- Position management with leverage controls (1x-100x)
- Complete trade form with 'You're paying' and 'You're receiving' sections
- Working dropdowns for token selection with balance display
- Professional position table with P&L tracking
- Real trade execution with backend API integration
- Stop Loss and Take Profit functionality
- Risk warnings for leveraged positions
- MAX button for using full balance
- Live market data integration
- Temporary chart placeholder (SimpleChart to be added)

All form sections complete, builds successfully, ready for testing
2025-07-16 16:04:04 +02:00

496 lines
20 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import React, { useState, useEffect, ChangeEvent } from 'react'
interface TradeModalProps {
isOpen: boolean
onClose: () => void
tradeData: {
entry: string
tp: string
tp2?: string
sl: string
symbol?: string
timeframe?: string
risk?: string
reward?: string
action?: 'BUY' | 'SELL'
} | null
onExecute: (data: any) => void
}
interface FormData {
entry: string
tp1: string
tp2: string
sl: string
positionValue: string
leverage: number
tradingCoin: string
tp1Percentage: number
tp2Percentage: number
}
const supportedCoins = [
{ symbol: 'SOL', name: 'Solana', icon: '◎', price: 159.5 },
{ symbol: 'USDC', name: 'USD Coin', icon: '$', price: 1.0 }
]
export default function TradeModal({ isOpen, onClose, tradeData, onExecute }: TradeModalProps) {
console.log('🚀 TradeModal loaded with enhanced features - Version 2.0')
const [loading, setLoading] = useState(false)
const [walletBalance, setWalletBalance] = useState<any>(null)
const [balanceLoading, setBalanceLoading] = useState(false)
const [formData, setFormData] = useState<FormData>({
entry: tradeData?.entry || '',
tp1: tradeData?.tp || '',
tp2: '',
sl: tradeData?.sl || '',
positionValue: '64', // USD amount for position size
leverage: 3,
tradingCoin: 'SOL',
tp1Percentage: 50, // % of position to close at TP1
tp2Percentage: 50 // % of position to close at TP2
})
// Fetch wallet balance when modal opens
useEffect(() => {
if (isOpen) {
fetchWalletBalance()
}
}, [isOpen])
// Update form data when tradeData changes
useEffect(() => {
if (tradeData) {
console.log('🔄 TradeModal updating form with new tradeData:', tradeData)
// Extract the base symbol (remove USD suffix)
let baseSymbol = 'SOL' // Default
if (tradeData.symbol) {
if (tradeData.symbol.endsWith('USD')) {
baseSymbol = tradeData.symbol.replace('USD', '')
} else {
baseSymbol = tradeData.symbol
}
}
console.log(`🔄 Setting trading coin to: ${baseSymbol} (from symbol: ${tradeData.symbol})`)
setFormData(prev => ({
...prev,
entry: tradeData.entry || '',
tp1: tradeData.tp || '',
tp2: tradeData.tp2 || '',
sl: tradeData.sl || '',
tradingCoin: baseSymbol
}))
}
}, [tradeData])
const fetchWalletBalance = async () => {
try {
setBalanceLoading(true)
console.log('💰 Fetching wallet balance...')
const response = await fetch('/api/wallet/balance')
const data = await response.json()
if (data.success) {
setWalletBalance(data)
console.log('✅ Wallet balance loaded:', data)
} else {
console.error('❌ Wallet balance API error:', data.error)
// Set fallback balance
setWalletBalance({
wallet: { solBalance: 2.5 },
balance: { availableBalance: 420.0 }
})
}
} catch (error) {
console.error('❌ Failed to fetch wallet balance:', error)
// Set fallback balance
setWalletBalance({
wallet: { solBalance: 2.5 },
balance: { availableBalance: 420.0 }
})
} finally {
setBalanceLoading(false)
}
}
// Helper function to safely format numbers
const formatNumber = (value: number, decimals: number = 2): string => {
return isNaN(value) ? '0.00' : value.toFixed(decimals)
}
// Calculate various metrics
const currentCoin = supportedCoins.find(coin => coin.symbol === formData.tradingCoin)
const coinPrice = currentCoin?.price || 159.5 // Default to SOL price
// Position calculations
const positionValueUSD = parseFloat(formData.positionValue) || 0
const positionSizeSOL = positionValueUSD / coinPrice // Calculate SOL amount from USD
const leverageNum = formData.leverage || 1
const entryPrice = parseFloat(formData.entry) || 0
const tp1Price = parseFloat(formData.tp1) || 0
const tp2Price = parseFloat(formData.tp2) || 0
const slPrice = parseFloat(formData.sl) || 0
// Profit calculations
const leveragedValue = positionValueUSD * leverageNum
// TP1 Profit calculation (percentage-based)
const profitTP1 = (entryPrice > 0 && tp1Price > 0 && leveragedValue > 0) ?
((tp1Price - entryPrice) / entryPrice) * leveragedValue * (formData.tp1Percentage / 100) : 0
// TP2 Profit calculation (percentage-based)
const profitTP2 = (entryPrice > 0 && tp2Price > 0 && leveragedValue > 0) ?
((tp2Price - entryPrice) / entryPrice) * leveragedValue * (formData.tp2Percentage / 100) : 0
// Stop Loss calculation
const lossAtSL = (entryPrice > 0 && slPrice > 0 && leveragedValue > 0) ?
((slPrice - entryPrice) / entryPrice) * leveragedValue : 0
const totalPotentialProfit = profitTP1 + profitTP2
if (!isOpen) return null
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
try {
console.log('🎯 Executing trade with data:', formData)
// Validation
if (!formData.positionValue || parseFloat(formData.positionValue) <= 0) {
alert('Please enter a valid position size')
setLoading(false)
return
}
if (!formData.entry || parseFloat(formData.entry) <= 0) {
alert('Please enter a valid entry price')
setLoading(false)
return
}
const tradingData = {
symbol: formData.tradingCoin + 'USD', // e.g., 'SOLUSD'
positionSize: formData.positionValue, // API expects 'positionSize'
size: formData.positionValue, // Fallback field name
amount: positionSizeSOL, // Send actual SOL amount, not USD amount
amountUSD: parseFloat(formData.positionValue), // USD amount for validation
sl: formData.sl,
tp1: formData.tp1,
tp2: formData.tp2,
entry: formData.entry,
leverage: formData.leverage,
positionSizeSOL: formatNumber(positionSizeSOL, 4),
leveragedValue: formatNumber(leveragedValue, 2),
profitTP1: formatNumber(profitTP1, 2),
profitTP2: formatNumber(profitTP2, 2),
totalProfit: formatNumber(totalPotentialProfit, 2),
lossAtSL: formatNumber(lossAtSL, 2)
}
console.log('🚀 Sending trading data to API:', tradingData)
await onExecute(tradingData)
onClose()
} catch (error) {
console.error('Trade execution failed:', error)
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
alert(`Trade execution failed: ${errorMessage}`)
} finally {
setLoading(false)
}
}
const setPositionPercentage = (percentage: number) => {
if (walletBalance && walletBalance.balance) {
// Use the available balance in USD from the API
const availableBalance = walletBalance.balance.availableBalance || 0
const newPosition = (availableBalance * percentage / 100).toFixed(0)
setFormData(prev => ({ ...prev, positionValue: newPosition }))
console.log(`🎯 Set position to ${percentage}% of available balance: $${newPosition}`)
} else {
console.warn('⚠️ Wallet balance not available for percentage calculation')
}
}
return (
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4">
<div className="bg-gray-900 rounded-2xl shadow-2xl w-full max-w-2xl max-h-[95vh] overflow-hidden border border-gray-700">
{/* Header */}
<div className="flex justify-between items-center p-6 border-b border-gray-700 bg-gradient-to-r from-blue-600/20 to-purple-600/20">
<div>
<h2 className="text-2xl font-bold text-white">Execute Trade</h2>
<p className="text-gray-400 text-sm">Configure your position details</p>
</div>
<button
onClick={onClose}
className="text-gray-400 hover:text-white text-2xl font-bold w-8 h-8 flex items-center justify-center rounded-full hover:bg-gray-700 transition-all"
>
×
</button>
</div>
<form onSubmit={handleSubmit} className="p-6 overflow-y-auto max-h-[calc(95vh-120px)]">
{/* Coin Selection */}
<div className="mb-6">
<label className="block text-sm font-medium text-gray-300 mb-3">Trading Coin</label>
<div className="grid grid-cols-2 gap-3">
{supportedCoins.map((coin) => (
<button
key={coin.symbol}
type="button"
onClick={() => setFormData(prev => ({ ...prev, tradingCoin: coin.symbol }))}
className={`p-4 rounded-xl border-2 transition-all duration-200 ${
formData.tradingCoin === coin.symbol
? 'border-blue-500 bg-blue-500/20 text-white'
: 'border-gray-600 bg-gray-800 text-gray-300 hover:border-gray-500'
}`}
>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<span className="text-2xl">{coin.icon}</span>
<div className="text-left">
<div className="font-semibold">{coin.symbol}</div>
<div className="text-xs text-gray-400">{coin.name}</div>
</div>
</div>
<div className="text-right">
<div className="font-mono text-sm">${formatNumber(coin.price)}</div>
</div>
</div>
</button>
))}
</div>
</div>
{/* Position Size */}
<div className="mb-6">
<div className="flex justify-between items-center mb-3">
<label className="text-sm font-medium text-gray-300">Position Size (USD)</label>
<div className="text-xs text-gray-400">
{balanceLoading ? (
<span className="animate-pulse">Loading balance...</span>
) : walletBalance ? (
<span>
Available: <span className="text-green-400 font-medium">
${formatNumber(walletBalance.balance?.availableBalance || 0, 2)}
</span>
</span>
) : (
<span className="text-red-400">Balance unavailable</span>
)}
</div>
</div>
<div className="space-y-3">
<input
type="number"
value={formData.positionValue}
onChange={(e) => setFormData(prev => ({ ...prev, positionValue: e.target.value }))}
className="w-full px-4 py-3 bg-gray-800 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-blue-500 transition-colors"
placeholder="Enter USD amount"
/>
{/* Quick percentage buttons */}
<div className="grid grid-cols-4 gap-2">
{[25, 50, 75, 100].map((percent) => (
<button
key={percent}
type="button"
disabled={balanceLoading || !walletBalance}
onClick={() => setPositionPercentage(percent)}
className={`py-2 px-3 rounded-lg text-xs transition-all ${
balanceLoading || !walletBalance
? 'bg-gray-800 text-gray-500 cursor-not-allowed'
: 'bg-gray-700 hover:bg-gray-600 text-gray-300 hover:text-white'
}`}
>
{percent}%
</button>
))}
</div>
{/* Position info */}
<div className="text-xs text-gray-400 space-y-1">
<div>Position Size: {formatNumber(positionSizeSOL, 4)} {formData.tradingCoin}</div>
<div>USD Value: ${formatNumber(positionValueUSD, 2)}</div>
</div>
</div>
</div>
{/* Leverage */}
<div className="mb-6">
<div className="flex justify-between items-center mb-3">
<label className="text-sm font-medium text-gray-300">Leverage</label>
<span className="text-sm text-blue-400 font-mono">{formData.leverage}x</span>
</div>
<input
type="range"
min="1"
max="10"
value={formData.leverage}
onChange={(e) => setFormData(prev => ({ ...prev, leverage: parseInt(e.target.value) }))}
className="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer range-slider"
/>
<div className="flex justify-between text-xs text-gray-500 mt-1">
<span>1x</span>
<span>5x</span>
<span>10x</span>
</div>
<div className="text-xs text-gray-400 mt-2">
Leveraged Value: ${formatNumber(leveragedValue, 2)}
</div>
</div>
{/* Price Inputs Grid */}
<div className="grid grid-cols-2 gap-4 mb-6">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Entry Price</label>
<input
type="number"
step="0.01"
value={formData.entry}
onChange={(e) => setFormData(prev => ({ ...prev, entry: e.target.value }))}
className="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-blue-500"
placeholder="0.00"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Stop Loss</label>
<input
type="number"
step="0.01"
value={formData.sl}
onChange={(e) => setFormData(prev => ({ ...prev, sl: e.target.value }))}
className="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-red-500"
placeholder="0.00"
/>
</div>
</div>
{/* Take Profit 1 */}
<div className="mb-6 p-4 bg-gray-800/50 rounded-xl border border-gray-700">
<div className="flex justify-between items-center mb-3">
<h3 className="text-sm font-medium text-gray-300">Take Profit 1</h3>
<span className="text-xs text-green-400">+${formatNumber(profitTP1, 2)}</span>
</div>
<div className="space-y-3">
<input
type="number"
step="0.01"
value={formData.tp1}
onChange={(e) => setFormData(prev => ({ ...prev, tp1: e.target.value }))}
className="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-green-500"
placeholder="TP1 Price"
/>
<div className="flex items-center space-x-3">
<span className="text-xs text-gray-400 min-w-[60px]">Allocation:</span>
<input
type="range"
min="10"
max="100"
step="10"
value={formData.tp1Percentage}
onChange={(e) => setFormData(prev => ({ ...prev, tp1Percentage: parseInt(e.target.value) }))}
className="flex-1 h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"
/>
<span className="text-xs text-green-400 min-w-[35px] font-mono">{formData.tp1Percentage}%</span>
</div>
</div>
</div>
{/* Take Profit 2 */}
<div className="mb-6 p-4 bg-gray-800/50 rounded-xl border border-gray-700">
<div className="flex justify-between items-center mb-3">
<h3 className="text-sm font-medium text-gray-300">Take Profit 2</h3>
<span className="text-xs text-green-400">+${formatNumber(profitTP2, 2)}</span>
</div>
<div className="space-y-3">
<input
type="number"
step="0.01"
value={formData.tp2}
onChange={(e) => setFormData(prev => ({ ...prev, tp2: e.target.value }))}
className="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-green-500"
placeholder="TP2 Price"
/>
<div className="flex items-center space-x-3">
<span className="text-xs text-gray-400 min-w-[60px]">Allocation:</span>
<input
type="range"
min="10"
max="100"
step="10"
value={formData.tp2Percentage}
onChange={(e) => setFormData(prev => ({ ...prev, tp2Percentage: parseInt(e.target.value) }))}
className="flex-1 h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"
/>
<span className="text-xs text-green-400 min-w-[35px] font-mono">{formData.tp2Percentage}%</span>
</div>
</div>
</div>
{/* Profit Summary */}
<div className="mb-6 p-4 bg-gradient-to-r from-green-500/10 to-blue-500/10 rounded-xl border border-green-500/20">
<h3 className="text-sm font-medium text-gray-300 mb-3">Profit Summary</h3>
<div className="grid grid-cols-2 gap-4 text-xs">
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-gray-400">TP1 Profit:</span>
<span className="text-green-400">+${formatNumber(profitTP1, 2)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">TP2 Profit:</span>
<span className="text-green-400">+${formatNumber(profitTP2, 2)}</span>
</div>
</div>
<div className="space-y-2">
<div className="flex justify-between">
<span className="text-gray-400">Total Profit:</span>
<span className="text-green-400 font-semibold">+${formatNumber(totalPotentialProfit, 2)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-400">Max Loss:</span>
<span className="text-red-400">${formatNumber(Math.abs(lossAtSL), 2)}</span>
</div>
</div>
</div>
</div>
{/* Execute Button */}
<div className="flex space-x-3">
<button
type="button"
onClick={onClose}
className="flex-1 py-3 px-6 bg-gray-700 text-gray-300 rounded-xl hover:bg-gray-600 transition-all duration-200"
>
Cancel
</button>
<button
type="submit"
disabled={loading || !formData.entry || !formData.tp1 || !formData.sl}
className={`flex-1 py-3 px-6 rounded-xl font-semibold transition-all duration-200 ${
loading || !formData.entry || !formData.tp1 || !formData.sl
? 'bg-gray-700 text-gray-400 cursor-not-allowed'
: 'bg-gradient-to-r from-green-500 to-green-600 text-white hover:from-green-600 hover:to-green-700 transform hover:scale-[1.02]'
}`}
>
{loading ? (
<div className="flex items-center justify-center space-x-2">
<div className="w-4 h-4 border-2 border-gray-400 border-t-transparent rounded-full animate-spin"></div>
<span>Executing...</span>
</div>
) : (
'Execute Trade'
)}
</button>
</div>
</form>
</div>
</div>
)
}