diff --git a/src/apps/insights/components/SignalCard/SignalCard.tsx b/src/apps/insights/components/SignalCard/SignalCard.tsx index 42cf0d7a..40beda1e 100644 --- a/src/apps/insights/components/SignalCard/SignalCard.tsx +++ b/src/apps/insights/components/SignalCard/SignalCard.tsx @@ -4,16 +4,18 @@ import { forwardRef, useState } from 'react'; import { motion } from 'framer-motion'; -import { - Clock, - TrendingUp, - Target, - XCircle, - RefreshCw, - CheckCircle2, - ChevronUp, +import { + Clock, + TrendingUp, + Target, + XCircle, + RefreshCw, + CheckCircle2, + ChevronUp, ChevronDown, - ArrowUpCircle + ArrowUpCircle, + Copy, + Check } from 'lucide-react'; import { Badge } from '../ui/badge'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip'; @@ -35,7 +37,29 @@ export const SignalCard = forwardRef( ({ signal, leverage, sparklineData, logoMap, animateOnMount = true }, ref) => { const applyLeverage = (pnl: number) => pnl * leverage; const [timelineOpen, setTimelineOpen] = useState(false); - + const [copied, setCopied] = useState(false); + + const copyStrategy = async () => { + const strategy = { + orderSide: signal.order_side, + ticker: signal.ticker, + exchange: "BINANCE", + entryPrice: signal.entry_price, + stopLoss: signal.stop_loss, + tp1: signal.tp1, + tp2: signal.tp2, + tp3: signal.tp3 + }; + + try { + await navigator.clipboard.writeText(JSON.stringify(strategy)); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error('Failed to copy strategy:', err); + } + }; + const getTimeSinceCreated = () => { const now = new Date(); const created = new Date(signal.created_at); @@ -43,7 +67,7 @@ export const SignalCard = forwardRef( const diffMinutes = Math.floor(diffMs / (1000 * 60)); const diffHours = Math.floor(diffMinutes / 60); const diffDays = Math.floor(diffHours / 24); - + if (diffDays > 0) { const remainingHours = diffHours % 24; return remainingHours > 0 ? `${diffDays}d ${remainingHours}h` : `${diffDays}d`; @@ -75,10 +99,10 @@ export const SignalCard = forwardRef( {(() => { const symbol = normalizeSymbol(signal.ticker); const logoUrl = logoMap[symbol]; - + return logoUrl ? ( <> - {signal.ticker.replace('.P',( -
- - {getTimeSinceCreated()} +
+ {isOpen && ( + + )} +
+ + {getTimeSinceCreated()} +
@@ -139,11 +183,11 @@ export const SignalCard = forwardRef(

Stop Loss

{(() => { const isTrailing = hasTrailingStop(signal) || ( - signal.order_side === 'buy' - ? signal.stop_loss > signal.entry_price + signal.order_side === 'buy' + ? signal.stop_loss > signal.entry_price : signal.stop_loss < signal.entry_price ); - + return isTrailing ? (

{signal.order_side === 'buy' ? '↑' : '↓'} @@ -182,19 +226,19 @@ export const SignalCard = forwardRef( ].filter(({ price }) => price != null).map(({ key, price, hit }) => { const isClosed = ['completed', 'stopped', 'closed'].includes(signal.status || 'active'); const priceReachedTP = signal.current_price && ( - signal.order_side === 'buy' - ? signal.current_price >= price + signal.order_side === 'buy' + ? signal.current_price >= price : signal.current_price <= price ); const isHit = hit || (isClosed && priceReachedTP); - + const tpPercent = signal.order_side?.toLowerCase() === 'sell' ? ((signal.entry_price - price) / signal.entry_price) * 100 : ((price - signal.entry_price) / signal.entry_price) * 100; const lockedInPercent = isHit ? (tpPercent * 0.3333) : null; const displayedLockedIn = lockedInPercent !== null ? applyLeverage(lockedInPercent).toFixed(2) : null; const displayedPotential = applyLeverage(tpPercent * 0.3333).toFixed(2); - + return ( @@ -245,7 +289,7 @@ export const SignalCard = forwardRef( )} - + {timelineOpen && (

{timeline.map((event, idx) => { @@ -256,10 +300,10 @@ export const SignalCard = forwardRef( RefreshCw, CheckCircle2, }[event.icon]; - + return ( -
{IconComponent && } @@ -299,9 +343,8 @@ export const SignalCard = forwardRef( <>

Unrealized P/L

-

= 0 ? 'text-[hsl(142,76%,58%)]' : 'text-[hsl(348,83%,58%)]' - }`}> +

= 0 ? 'text-[hsl(142,76%,58%)]' : 'text-[hsl(348,83%,58%)]' + }`}> {applyLeverage(signal.profit_loss_percent || 0) >= 0 ? '+' : ''}{applyLeverage(signal.profit_loss_percent || 0).toFixed(2).replace('.', ',')}% {(signal.profit_loss_percent || 0) >= 0 && }

@@ -317,9 +360,8 @@ export const SignalCard = forwardRef( <>

Realized P/L

-

= 0 ? 'text-[hsl(142,76%,58%)]' : 'text-[hsl(348,83%,58%)]' - }`}> +

= 0 ? 'text-[hsl(142,76%,58%)]' : 'text-[hsl(348,83%,58%)]' + }`}> {applyLeverage(signal.realized_pnl_percent || 0) >= 0 ? '+' : ''}{applyLeverage(signal.realized_pnl_percent || 0).toFixed(2).replace('.', ',')}% {(signal.realized_pnl_percent || 0) >= 0 && }

diff --git a/src/apps/insights/hooks/useTradingSignals.ts b/src/apps/insights/hooks/useTradingSignals.ts index 3b449aeb..d88d469f 100644 --- a/src/apps/insights/hooks/useTradingSignals.ts +++ b/src/apps/insights/hooks/useTradingSignals.ts @@ -27,17 +27,18 @@ export const useTradingSignals = (options: UseTradingSignalsOptions = {}) => { if (!isRefresh) { setLoading(true); } + const result = await getTradingSignals(); console.log('🔍 [useTradingSignals] API response:', result); - + if (result.error) { throw result.error; } - + // Firebase function returns { signals: [...] } const signalsArray = (result.signals || result.data || []) as TradingSignal[]; console.log(`✅ [useTradingSignals] Loaded ${signalsArray.length} signals:`, signalsArray); - + // Log first signal structure for debugging if (signalsArray.length > 0) { const firstSignal = signalsArray[0]; @@ -51,10 +52,10 @@ export const useTradingSignals = (options: UseTradingSignalsOptions = {}) => { entry_price: firstSignal.entry_price, }); } - + setSignals(signalsArray); setError(null); - + // Mark initial load as complete if (isInitialLoad) { setIsInitialLoad(false); @@ -66,7 +67,7 @@ export const useTradingSignals = (options: UseTradingSignalsOptions = {}) => { } finally { setLoading(false); } - }, [enabled]); + }, [enabled, isInitialLoad]); useEffect(() => { if (!enabled) { diff --git a/src/apps/insights/index.tsx b/src/apps/insights/index.tsx index 4491783e..b4904052 100644 --- a/src/apps/insights/index.tsx +++ b/src/apps/insights/index.tsx @@ -124,6 +124,7 @@ const App = () => { enabled: Boolean(eoaAddress), pollIntervalMs: SUBSCRIPTION_POLL_INTERVAL, }); + const [isAwaitingSubscription, setIsAwaitingSubscription] = useState(false); const [showManageMenu, setShowManageMenu] = useState(false); const manageMenuRef = useRef(null); @@ -161,7 +162,7 @@ const App = () => { const consentDate = new Date(consent.timestamp); const oneYearAgo = new Date(); oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1); - + if (consentDate > oneYearAgo) { setConsentGiven(true); } else { @@ -187,22 +188,22 @@ const App = () => { if (signals.length > 0 && !loading) { const openSignals = signals.filter(s => s.status === 'active'); - + // Only fetch sparklines for signals we haven't fetched yet const signalsNeedingSparklines = openSignals.filter(signal => { const alreadyFetched = fetchedSparklineIdsRef.current.has(signal.id); const hasData = sparklineDataMap[signal.id] && sparklineDataMap[signal.id].length > 0; const isLoading = sparklineLoading[signal.id]; - + if (alreadyFetched || hasData || isLoading) { return false; } - + // Mark as fetched to prevent duplicate requests fetchedSparklineIdsRef.current.add(signal.id); return true; }); - + if (signalsNeedingSparklines.length > 0) { console.log(`📊 [Sparkline] Initial fetch for ${signalsNeedingSparklines.length} new signals`); fetchSparklines(signalsNeedingSparklines); @@ -258,7 +259,7 @@ const App = () => { } return value; }, [openSignals]); - + const closedTotalPnL = useMemo(() => { const closedPnL = calculateTotalPnL(closedSignals); const openRealizedPnL = calculateOpenRealizedPnL(); @@ -266,7 +267,7 @@ const App = () => { console.log(`💰 [PnL] closedTotalPnL: ${value}% (closed: ${closedPnL}%, open realized: ${openRealizedPnL}%)`); return value; }, [closedSignals, openSignals]); - + const floatingPnL = useMemo(() => { const value = openTotalPnL + closedTotalPnL; console.log(`💰 [PnL] floatingPnL: ${value}% (open: ${openTotalPnL}%, closed: ${closedTotalPnL}%)`); @@ -305,7 +306,7 @@ const App = () => { }; const handleRefreshSubscription = useCallback(() => { - refetchSubscription().catch(() => {}); + refetchSubscription().catch(() => { }); }, [refetchSubscription]); const handleSubscribeClick = useCallback(() => { @@ -324,7 +325,7 @@ const App = () => { setIsAwaitingSubscription(true); startPolling(); - refetchSubscription().catch(() => {}); + refetchSubscription().catch(() => { }); if (!isNativeApp) { const confirmed = window.confirm( @@ -642,9 +643,9 @@ const App = () => {
) : ( feedEvents.map((event) => ( -