@hanzo/ui 5.1.7 → 5.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/3d/button.js +1 -0
- package/dist/3d/button.mjs +1 -0
- package/dist/3d/card.js +1 -0
- package/dist/3d/card.mjs +1 -0
- package/dist/3d/carousel.js +1 -0
- package/dist/3d/carousel.mjs +1 -0
- package/dist/3d/grid.js +1 -0
- package/dist/3d/grid.mjs +1 -0
- package/dist/3d/index.js +1 -0
- package/dist/3d/index.mjs +1 -0
- package/dist/3d/marquee.js +1 -0
- package/dist/3d/marquee.mjs +1 -0
- package/dist/3d/pin.js +1 -0
- package/dist/3d/pin.mjs +1 -0
- package/dist/animation/apple-cards-carousel.js +1 -0
- package/dist/animation/apple-cards-carousel.mjs +1 -0
- package/dist/animation/apple-hello-effect.js +1 -0
- package/dist/animation/apple-hello-effect.mjs +1 -0
- package/dist/animation/beam.js +1 -0
- package/dist/animation/beam.mjs +1 -0
- package/dist/animation/cursor.js +1 -0
- package/dist/animation/cursor.mjs +1 -0
- package/dist/animation/index.js +2 -0
- package/dist/animation/index.mjs +2 -0
- package/dist/animation/testimonials.js +1 -0
- package/dist/animation/testimonials.mjs +1 -0
- package/dist/animation/tooltip.js +1 -0
- package/dist/animation/tooltip.mjs +1 -0
- package/dist/avatar.js +1 -1
- package/dist/avatar.mjs +1 -1
- package/dist/chunk-266OC746.mjs +2 -0
- package/dist/chunk-2B7NONPA.mjs +1 -0
- package/dist/chunk-2SI3T6F4.mjs +1 -0
- package/dist/chunk-37TVHMP2.mjs +1 -0
- package/dist/chunk-4A27XTBF.js +12 -0
- package/dist/chunk-4CJ44NSE.js +1 -0
- package/dist/chunk-4SKGA2GA.mjs +1 -0
- package/dist/chunk-4TE2JWNG.js +1 -0
- package/dist/chunk-4XCQFT2C.mjs +1 -0
- package/dist/chunk-4YMLAU5Z.js +1 -0
- package/dist/chunk-5RACWNGV.mjs +1 -0
- package/dist/chunk-A3S2QAGG.mjs +3 -0
- package/dist/chunk-B3L4REN3.js +1 -0
- package/dist/chunk-B4RJSSWX.js +1 -0
- package/dist/chunk-B7LBF7R7.js +1 -0
- package/dist/chunk-BIWGFJPQ.mjs +1 -0
- package/dist/chunk-BOBL44QL.mjs +2 -0
- package/dist/chunk-BUMLLGES.mjs +1 -0
- package/dist/chunk-BYFLZ6DP.js +1 -0
- package/dist/chunk-C66RILAI.mjs +42 -0
- package/dist/chunk-CDBOLI7U.js +42 -0
- package/dist/chunk-CDO4BR6V.js +1 -0
- package/dist/chunk-CGUI5CEC.mjs +1 -0
- package/dist/chunk-CICZVOAY.js +1 -0
- package/dist/chunk-DB3WJTS4.js +1 -0
- package/dist/chunk-DKL2FM7J.mjs +1 -0
- package/dist/chunk-DNYROKHM.mjs +1 -0
- package/dist/chunk-DRLJ6Z7Q.js +1 -0
- package/dist/chunk-DYF5HRXR.mjs +1 -0
- package/dist/chunk-EATSKPYS.js +10 -0
- package/dist/chunk-EGN6SLB7.js +3 -0
- package/dist/chunk-EK5T4DQ2.js +2 -0
- package/dist/chunk-ELR6GOUX.mjs +1 -0
- package/dist/chunk-EQAPJD4H.mjs +1 -0
- package/dist/chunk-FCHJPQ73.mjs +1 -0
- package/dist/chunk-FE2O6776.js +1 -0
- package/dist/chunk-FIN2IX2D.mjs +1 -0
- package/dist/chunk-FQO25CNQ.mjs +1 -0
- package/dist/chunk-FX7VUERX.mjs +2 -0
- package/dist/chunk-G4X4J5EW.js +1 -0
- package/dist/chunk-GFWDGZO7.mjs +1 -0
- package/dist/chunk-GTFU3T2U.mjs +1 -0
- package/dist/chunk-GTUCZ5TW.js +1 -0
- package/dist/chunk-HEZV2WAY.js +2 -0
- package/dist/chunk-HIDGPSBX.js +1 -0
- package/dist/chunk-HLJAWNJK.js +1 -0
- package/dist/chunk-HM47SDFC.mjs +1 -0
- package/dist/chunk-HQGRRTIW.mjs +2 -0
- package/dist/chunk-J4DJZHOE.js +2 -0
- package/dist/chunk-J5KX3VTM.js +1 -0
- package/dist/chunk-JNNKXOA3.js +1 -0
- package/dist/chunk-JRWBF5SH.js +1 -0
- package/dist/chunk-JTTJLJON.js +2 -0
- package/dist/chunk-JZGTSHZ4.js +1 -0
- package/dist/chunk-LADFCKQD.mjs +1 -0
- package/dist/chunk-LHKPSY4L.js +1 -0
- package/dist/chunk-LMOX4GTY.js +1 -0
- package/dist/chunk-LYXX7LU2.js +6 -0
- package/dist/chunk-M4GZKMQS.mjs +6 -0
- package/dist/chunk-MJDVKTGX.mjs +1 -0
- package/dist/chunk-MPZRRATY.mjs +1 -0
- package/dist/chunk-MUI6SJLP.js +1 -0
- package/dist/chunk-NBXNOMXS.mjs +2 -0
- package/dist/chunk-NE7ICOQE.mjs +2 -0
- package/dist/chunk-NL5ZWDIJ.mjs +1 -0
- package/dist/chunk-NS6CEKHH.mjs +1 -0
- package/dist/chunk-O6RJT46G.js +1 -0
- package/dist/chunk-OC34KEWQ.mjs +1 -0
- package/dist/chunk-OL63HLUI.js +2 -0
- package/dist/chunk-OLTPOUB2.js +2 -0
- package/dist/chunk-OQOBF7QQ.js +1 -0
- package/dist/chunk-OY2LIJR2.mjs +2 -0
- package/dist/chunk-PE5GANLF.mjs +1 -0
- package/dist/chunk-PIDMTL36.mjs +1 -0
- package/dist/chunk-QGHPGSQL.mjs +2 -0
- package/dist/chunk-QLB43TFO.mjs +1 -0
- package/dist/chunk-R2JKVWNB.js +2 -0
- package/dist/chunk-RAT4U2JG.mjs +1 -0
- package/dist/chunk-RBURLSG7.js +2 -0
- package/dist/chunk-RO3D3W57.js +1 -0
- package/dist/chunk-RPOB4E3W.js +1 -0
- package/dist/chunk-RVPP46TI.js +2 -0
- package/dist/chunk-S2UB25HB.mjs +12 -0
- package/dist/chunk-SCYHSLR2.js +2 -0
- package/dist/chunk-SRPQZYB6.js +1 -0
- package/dist/chunk-TCTFHNND.js +1 -0
- package/dist/chunk-TG6V2JL2.js +1 -0
- package/dist/chunk-TGL6VBFN.js +1 -0
- package/dist/chunk-TNF5G2ZW.mjs +1 -0
- package/dist/chunk-TXND3LRQ.mjs +1 -0
- package/dist/chunk-U7CUKU4W.mjs +1 -0
- package/dist/chunk-UPRWI4ON.mjs +2 -0
- package/dist/chunk-V23X5ZD6.mjs +2 -0
- package/dist/chunk-VFFLOHVQ.mjs +1 -0
- package/dist/chunk-VHB7M646.mjs +10 -0
- package/dist/chunk-VJDDFZ7N.mjs +2 -0
- package/dist/chunk-VN2BKXHJ.js +1 -0
- package/dist/chunk-VT77IEYF.mjs +1 -0
- package/dist/chunk-VTJ6QUCR.js +1 -0
- package/dist/chunk-VUIJAFJC.js +1 -0
- package/dist/chunk-WN767YS2.mjs +2 -0
- package/dist/chunk-WREKV6XQ.mjs +1 -0
- package/dist/chunk-XBDQSMTI.js +2 -0
- package/dist/chunk-XFK7RTKE.js +1 -0
- package/dist/chunk-XJTWK37Q.mjs +2 -0
- package/dist/chunk-XMYCJ2B5.js +1 -0
- package/dist/chunk-XOGRJZXX.mjs +1 -0
- package/dist/chunk-XOQCRR5Q.js +2 -0
- package/dist/chunk-Y6SIRKG3.js +2 -0
- package/dist/chunk-YLY2B6UX.mjs +1 -0
- package/dist/chunk-YQUF7KPG.mjs +1 -0
- package/dist/chunk-Z3DNBRF4.js +1 -0
- package/dist/chunk-ZA3JIBDG.js +1 -0
- package/dist/chunk-ZDOIGP2L.js +1 -0
- package/dist/code/block.js +1 -0
- package/dist/code/block.mjs +1 -0
- package/dist/code/compare.js +1 -0
- package/dist/code/compare.mjs +1 -0
- package/dist/code/diff.js +1 -0
- package/dist/code/diff.mjs +1 -0
- package/dist/code/editor.js +1 -0
- package/dist/code/editor.mjs +1 -0
- package/dist/code/explorer.js +1 -0
- package/dist/code/explorer.mjs +1 -0
- package/dist/code/index.js +1 -0
- package/dist/code/index.mjs +1 -0
- package/dist/code/preview.js +1 -0
- package/dist/code/preview.mjs +1 -0
- package/dist/code/snippet.js +1 -0
- package/dist/code/snippet.mjs +1 -0
- package/dist/code/tabs.js +1 -0
- package/dist/code/tabs.mjs +1 -0
- package/dist/code/terminal.js +1 -0
- package/dist/code/terminal.mjs +1 -0
- package/dist/device/index.js +1 -0
- package/dist/device/index.mjs +0 -0
- package/dist/dock/basic.js +1 -0
- package/dist/dock/basic.mjs +1 -0
- package/dist/dock/index.js +1 -0
- package/dist/dock/index.mjs +1 -0
- package/dist/dock/limelight-nav.js +1 -0
- package/dist/dock/limelight-nav.mjs +1 -0
- package/dist/dock/macos.js +1 -0
- package/dist/dock/macos.mjs +1 -0
- package/dist/dock/menu.js +1 -0
- package/dist/dock/menu.mjs +1 -0
- package/dist/dock/message.js +1 -0
- package/dist/dock/message.mjs +1 -0
- package/dist/finance/AdvancedChart.js +1 -0
- package/dist/finance/AdvancedChart.mjs +1 -0
- package/dist/finance/CompanyProfile.js +1 -0
- package/dist/finance/CompanyProfile.mjs +1 -0
- package/dist/finance/CryptoScreener.js +1 -0
- package/dist/finance/CryptoScreener.mjs +1 -0
- package/dist/finance/Financials.js +1 -0
- package/dist/finance/Financials.mjs +1 -0
- package/dist/finance/ForexScreener.js +1 -0
- package/dist/finance/ForexScreener.mjs +1 -0
- package/dist/finance/MarketOverview.js +1 -0
- package/dist/finance/MarketOverview.mjs +1 -0
- package/dist/finance/NewsTimeline.js +1 -0
- package/dist/finance/NewsTimeline.mjs +1 -0
- package/dist/finance/OrderEntry.js +1 -0
- package/dist/finance/OrderEntry.mjs +1 -0
- package/dist/finance/OrdersHistory.js +1 -0
- package/dist/finance/OrdersHistory.mjs +1 -0
- package/dist/finance/PositionsList.js +1 -0
- package/dist/finance/PositionsList.mjs +1 -0
- package/dist/finance/StockScreener.js +1 -0
- package/dist/finance/StockScreener.mjs +1 -0
- package/dist/finance/SymbolInfo.js +1 -0
- package/dist/finance/SymbolInfo.mjs +1 -0
- package/dist/finance/TechnicalAnalysis.js +1 -0
- package/dist/finance/TechnicalAnalysis.mjs +1 -0
- package/dist/finance/TickerTape.js +1 -0
- package/dist/finance/TickerTape.mjs +1 -0
- package/dist/finance/TradingPanel.js +1 -0
- package/dist/finance/TradingPanel.mjs +1 -0
- package/dist/finance/index.js +1 -0
- package/dist/finance/index.mjs +1 -0
- package/dist/form/index.js +1 -0
- package/dist/form/index.mjs +1 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/navigation/index.js +1 -0
- package/dist/navigation/index.mjs +1 -0
- package/dist/pattern/grid.js +1 -0
- package/dist/pattern/grid.mjs +1 -0
- package/dist/pattern/index.js +1 -0
- package/dist/pattern/index.mjs +1 -0
- package/dist/primitives/index.js +1 -1
- package/dist/primitives/index.mjs +1 -1
- package/dist/project/gantt.js +1 -0
- package/dist/project/gantt.mjs +1 -0
- package/dist/project/index.js +1 -0
- package/dist/project/index.mjs +1 -0
- package/dist/project/kanban.js +1 -0
- package/dist/project/kanban.mjs +1 -0
- package/dist/project/list.js +1 -0
- package/dist/project/list.mjs +1 -0
- package/dist/ui/announcement.js +1 -0
- package/dist/ui/announcement.mjs +1 -0
- package/dist/ui/avatar-group.js +1 -0
- package/dist/ui/avatar-group.mjs +1 -0
- package/dist/ui/banner.js +1 -0
- package/dist/ui/banner.mjs +1 -0
- package/dist/ui/cursor.js +1 -0
- package/dist/ui/cursor.mjs +1 -0
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.mjs +1 -0
- package/dist/ui/marquee.js +1 -0
- package/dist/ui/marquee.mjs +1 -0
- package/dist/ui/pill.js +1 -0
- package/dist/ui/pill.mjs +1 -0
- package/dist/ui/spinner.js +1 -0
- package/dist/ui/spinner.mjs +1 -0
- package/dist/ui/tags.js +1 -0
- package/dist/ui/tags.mjs +1 -0
- package/dist/ui/ticker.js +1 -0
- package/dist/ui/ticker.mjs +1 -0
- package/finance/README.md +164 -0
- package/finance/components/AdvancedChart.tsx +58 -0
- package/finance/components/CompanyProfile.tsx +65 -0
- package/finance/components/CryptoScreener.tsx +55 -0
- package/finance/components/Financials.tsx +71 -0
- package/finance/components/ForexScreener.tsx +56 -0
- package/finance/components/MarketOverview.tsx +114 -0
- package/finance/components/NewsTimeline.tsx +54 -0
- package/finance/components/OrderEntry.tsx +157 -0
- package/finance/components/OrdersHistory.tsx +103 -0
- package/finance/components/PositionsList.tsx +85 -0
- package/finance/components/StockScreener.tsx +56 -0
- package/finance/components/SymbolInfo.tsx +62 -0
- package/finance/components/TechnicalAnalysis.tsx +74 -0
- package/finance/components/TickerTape.tsx +66 -0
- package/finance/components/TradingPanel.tsx +238 -0
- package/finance/components/index.ts +40 -0
- package/finance/index.ts +23 -0
- package/package.json +135 -9
- package/style/theme-provider.tsx +1 -1
- package/dist/chunk-BBXIHLTX.mjs +0 -1
- package/dist/chunk-EZN2P3FV.js +0 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
export interface Order {
|
|
4
|
+
id: string
|
|
5
|
+
symbol: string
|
|
6
|
+
type: 'buy' | 'sell'
|
|
7
|
+
shares: number
|
|
8
|
+
price: number
|
|
9
|
+
status: 'open' | 'filled' | 'cancelled' | 'pending'
|
|
10
|
+
timestamp: number
|
|
11
|
+
orderType?: 'market' | 'limit'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface OrdersHistoryProps {
|
|
15
|
+
orders: Order[]
|
|
16
|
+
onCancelOrder?: (orderId: string) => void
|
|
17
|
+
onOrderClick?: (order: Order) => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function OrdersHistory({ orders, onCancelOrder, onOrderClick }: OrdersHistoryProps) {
|
|
21
|
+
const getTickerFromSymbol = (symbol: string) => {
|
|
22
|
+
return symbol.split(':')[1] || symbol
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (orders.length === 0) {
|
|
26
|
+
return (
|
|
27
|
+
<div className="p-4 text-center py-8 text-white/40">
|
|
28
|
+
<p className="text-sm">No orders yet</p>
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="p-4 space-y-2">
|
|
35
|
+
{orders.map((order) => (
|
|
36
|
+
<div
|
|
37
|
+
key={order.id}
|
|
38
|
+
className={`bg-white/5 rounded-lg p-3 ${onOrderClick ? 'cursor-pointer hover:bg-white/10 transition-colors' : ''}`}
|
|
39
|
+
onClick={() => onOrderClick?.(order)}
|
|
40
|
+
>
|
|
41
|
+
<div className="flex items-start justify-between mb-2">
|
|
42
|
+
<div>
|
|
43
|
+
<div className="font-semibold text-white">
|
|
44
|
+
{getTickerFromSymbol(order.symbol)}
|
|
45
|
+
</div>
|
|
46
|
+
<div className="text-xs text-white/60">
|
|
47
|
+
{new Date(order.timestamp).toLocaleString()}
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
<div className={`text-xs px-2 py-1 rounded ${
|
|
51
|
+
order.status === 'filled'
|
|
52
|
+
? 'bg-success/20 text-success'
|
|
53
|
+
: order.status === 'cancelled'
|
|
54
|
+
? 'bg-white/10 text-white/60'
|
|
55
|
+
: order.status === 'pending'
|
|
56
|
+
? 'bg-yellow-500/20 text-yellow-400'
|
|
57
|
+
: 'bg-blue-500/20 text-blue-400'
|
|
58
|
+
}`}>
|
|
59
|
+
{order.status.charAt(0).toUpperCase() + order.status.slice(1)}
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div className="grid grid-cols-3 gap-2 text-xs">
|
|
63
|
+
<div>
|
|
64
|
+
<div className="text-white/60">Side</div>
|
|
65
|
+
<div className={`font-medium ${order.type === 'buy' ? 'text-success' : 'text-danger'}`}>
|
|
66
|
+
{order.type.toUpperCase()}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<div>
|
|
70
|
+
<div className="text-white/60">Shares</div>
|
|
71
|
+
<div className="text-white font-medium">{order.shares}</div>
|
|
72
|
+
</div>
|
|
73
|
+
<div>
|
|
74
|
+
<div className="text-white/60">Price</div>
|
|
75
|
+
<div className="text-white font-medium">
|
|
76
|
+
{order.price > 0 ? `$${order.price.toFixed(2)}` : 'Market'}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
{order.orderType && (
|
|
81
|
+
<div className="mt-2 text-xs">
|
|
82
|
+
<span className="text-white/60">Type: </span>
|
|
83
|
+
<span className="text-white font-medium">
|
|
84
|
+
{order.orderType.charAt(0).toUpperCase() + order.orderType.slice(1)}
|
|
85
|
+
</span>
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
{order.status === 'open' && onCancelOrder && (
|
|
89
|
+
<button
|
|
90
|
+
onClick={(e) => {
|
|
91
|
+
e.stopPropagation()
|
|
92
|
+
onCancelOrder(order.id)
|
|
93
|
+
}}
|
|
94
|
+
className="w-full mt-2 py-1.5 text-xs bg-white/5 hover:bg-white/10 text-white/80 rounded transition-colors"
|
|
95
|
+
>
|
|
96
|
+
Cancel Order
|
|
97
|
+
</button>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
))}
|
|
101
|
+
</div>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { TrendingUp, TrendingDown } from 'lucide-react'
|
|
4
|
+
|
|
5
|
+
export interface Position {
|
|
6
|
+
symbol: string
|
|
7
|
+
shares: number
|
|
8
|
+
avgPrice: number
|
|
9
|
+
currentPrice: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PositionsListProps {
|
|
13
|
+
positions: Position[]
|
|
14
|
+
onPositionClick?: (position: Position) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function PositionsList({ positions, onPositionClick }: PositionsListProps) {
|
|
18
|
+
const getTickerFromSymbol = (symbol: string) => {
|
|
19
|
+
return symbol.split(':')[1] || symbol
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (positions.length === 0) {
|
|
23
|
+
return (
|
|
24
|
+
<div className="p-4 text-center py-8 text-white/40">
|
|
25
|
+
<p className="text-sm">No open positions</p>
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="p-4 space-y-2">
|
|
32
|
+
{positions.map((position) => {
|
|
33
|
+
const pl = (position.currentPrice - position.avgPrice) * position.shares
|
|
34
|
+
const plPercent = ((position.currentPrice - position.avgPrice) / position.avgPrice) * 100
|
|
35
|
+
const totalValue = position.currentPrice * position.shares
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
key={position.symbol}
|
|
40
|
+
className={`bg-white/5 rounded-lg p-3 ${onPositionClick ? 'cursor-pointer hover:bg-white/10 transition-colors' : ''}`}
|
|
41
|
+
onClick={() => onPositionClick?.(position)}
|
|
42
|
+
>
|
|
43
|
+
<div className="flex items-center justify-between mb-2">
|
|
44
|
+
<div className="font-semibold text-white">
|
|
45
|
+
{getTickerFromSymbol(position.symbol)}
|
|
46
|
+
</div>
|
|
47
|
+
<div className={`flex items-center gap-1 text-sm ${pl >= 0 ? 'text-success' : 'text-danger'}`}>
|
|
48
|
+
{pl >= 0 ? <TrendingUp size={14} /> : <TrendingDown size={14} />}
|
|
49
|
+
{pl >= 0 ? '+' : ''}{plPercent.toFixed(2)}%
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
<div className="grid grid-cols-2 gap-2 text-xs">
|
|
53
|
+
<div>
|
|
54
|
+
<div className="text-white/60">Shares</div>
|
|
55
|
+
<div className="text-white font-medium">{position.shares}</div>
|
|
56
|
+
</div>
|
|
57
|
+
<div>
|
|
58
|
+
<div className="text-white/60">Avg Price</div>
|
|
59
|
+
<div className="text-white font-medium">${position.avgPrice.toFixed(2)}</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div className="grid grid-cols-2 gap-2 text-xs mt-2 pt-2 border-t border-white/10">
|
|
63
|
+
<div>
|
|
64
|
+
<div className="text-white/60">Current Price</div>
|
|
65
|
+
<div className="text-white font-medium">${position.currentPrice.toFixed(2)}</div>
|
|
66
|
+
</div>
|
|
67
|
+
<div>
|
|
68
|
+
<div className="text-white/60">Total Value</div>
|
|
69
|
+
<div className="text-white font-medium">${totalValue.toFixed(2)}</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
<div className="mt-2 pt-2 border-t border-white/10">
|
|
73
|
+
<div className="flex justify-between items-center text-xs">
|
|
74
|
+
<span className="text-white/60">P&L</span>
|
|
75
|
+
<span className={`font-semibold ${pl >= 0 ? 'text-success' : 'text-danger'}`}>
|
|
76
|
+
{pl >= 0 ? '+' : ''}${pl.toFixed(2)}
|
|
77
|
+
</span>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
)
|
|
82
|
+
})}
|
|
83
|
+
</div>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, memo } from 'react'
|
|
4
|
+
|
|
5
|
+
function StockScreener() {
|
|
6
|
+
const container = useRef<HTMLDivElement>(null)
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (!container.current) return
|
|
10
|
+
|
|
11
|
+
// Check if widget is already initialized using data attribute
|
|
12
|
+
if (container.current.dataset.initialized === 'true') {
|
|
13
|
+
return // Widget already initialized, skip
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Mark as initialized before adding script
|
|
17
|
+
container.current.dataset.initialized = 'true'
|
|
18
|
+
|
|
19
|
+
const script = document.createElement('script')
|
|
20
|
+
script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-screener.js'
|
|
21
|
+
script.type = 'text/javascript'
|
|
22
|
+
script.async = true
|
|
23
|
+
script.innerHTML = JSON.stringify({
|
|
24
|
+
market: 'america',
|
|
25
|
+
showToolbar: true,
|
|
26
|
+
defaultColumn: 'overview',
|
|
27
|
+
defaultScreen: 'most_capitalized',
|
|
28
|
+
isTransparent: false,
|
|
29
|
+
locale: 'en',
|
|
30
|
+
colorTheme: 'dark',
|
|
31
|
+
width: '100%',
|
|
32
|
+
height: 550,
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
container.current.appendChild(script)
|
|
36
|
+
|
|
37
|
+
// Cleanup function
|
|
38
|
+
return () => {
|
|
39
|
+
if (container.current) {
|
|
40
|
+
// Remove initialization flag on unmount
|
|
41
|
+
delete container.current.dataset.initialized
|
|
42
|
+
// Remove all scripts
|
|
43
|
+
const scripts = container.current.querySelectorAll('script')
|
|
44
|
+
scripts.forEach((s) => s.remove())
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}, [])
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div className="tradingview-widget-container w-full" ref={container}>
|
|
51
|
+
<div className="tradingview-widget-container__widget"></div>
|
|
52
|
+
</div>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default memo(StockScreener)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from 'react'
|
|
4
|
+
|
|
5
|
+
export interface SymbolInfoProps {
|
|
6
|
+
symbol?: string
|
|
7
|
+
width?: string | number
|
|
8
|
+
locale?: string
|
|
9
|
+
colorTheme?: 'light' | 'dark'
|
|
10
|
+
isTransparent?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function SymbolInfo({
|
|
14
|
+
symbol = 'NASDAQ:AAPL',
|
|
15
|
+
width = '100%',
|
|
16
|
+
locale = 'en',
|
|
17
|
+
colorTheme = 'dark',
|
|
18
|
+
isTransparent = false
|
|
19
|
+
}: SymbolInfoProps) {
|
|
20
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!containerRef.current) return
|
|
24
|
+
|
|
25
|
+
// Check if widget is already initialized using data attribute
|
|
26
|
+
if (containerRef.current.dataset.initialized === 'true') {
|
|
27
|
+
return // Widget already initialized, skip
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Mark as initialized before adding script
|
|
31
|
+
containerRef.current.dataset.initialized = 'true'
|
|
32
|
+
|
|
33
|
+
const script = document.createElement('script')
|
|
34
|
+
script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-symbol-info.js'
|
|
35
|
+
script.async = true
|
|
36
|
+
script.innerHTML = JSON.stringify({
|
|
37
|
+
symbol,
|
|
38
|
+
width,
|
|
39
|
+
locale,
|
|
40
|
+
colorTheme,
|
|
41
|
+
isTransparent,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
containerRef.current.appendChild(script)
|
|
45
|
+
|
|
46
|
+
return () => {
|
|
47
|
+
if (containerRef.current) {
|
|
48
|
+
// Remove initialization flag on unmount
|
|
49
|
+
delete containerRef.current.dataset.initialized
|
|
50
|
+
// Remove all scripts
|
|
51
|
+
const scripts = containerRef.current.querySelectorAll('script')
|
|
52
|
+
scripts.forEach((s) => s.remove())
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}, [symbol, width, locale, colorTheme, isTransparent])
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div className="tradingview-widget-container" ref={containerRef}>
|
|
59
|
+
<div className="tradingview-widget-container__widget"></div>
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from 'react'
|
|
4
|
+
|
|
5
|
+
export interface TechnicalAnalysisProps {
|
|
6
|
+
symbol?: string
|
|
7
|
+
interval?: '1m' | '5m' | '15m' | '30m' | '1h' | '2h' | '4h' | '1D' | '1W' | '1M'
|
|
8
|
+
width?: string | number
|
|
9
|
+
height?: string | number
|
|
10
|
+
locale?: string
|
|
11
|
+
colorTheme?: 'light' | 'dark'
|
|
12
|
+
isTransparent?: boolean
|
|
13
|
+
showIntervalTabs?: boolean
|
|
14
|
+
displayMode?: 'single' | 'multiple'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function TechnicalAnalysis({
|
|
18
|
+
symbol = 'NASDAQ:AAPL',
|
|
19
|
+
interval = '15m',
|
|
20
|
+
width = '100%',
|
|
21
|
+
height = '500',
|
|
22
|
+
locale = 'en',
|
|
23
|
+
colorTheme = 'dark',
|
|
24
|
+
isTransparent = false,
|
|
25
|
+
showIntervalTabs = true,
|
|
26
|
+
displayMode = 'single'
|
|
27
|
+
}: TechnicalAnalysisProps) {
|
|
28
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!containerRef.current) return
|
|
32
|
+
|
|
33
|
+
// Check if widget is already initialized using data attribute
|
|
34
|
+
if (containerRef.current.dataset.initialized === 'true') {
|
|
35
|
+
return // Widget already initialized, skip
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Mark as initialized before adding script
|
|
39
|
+
containerRef.current.dataset.initialized = 'true'
|
|
40
|
+
|
|
41
|
+
const script = document.createElement('script')
|
|
42
|
+
script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-technical-analysis.js'
|
|
43
|
+
script.async = true
|
|
44
|
+
script.innerHTML = JSON.stringify({
|
|
45
|
+
interval,
|
|
46
|
+
width,
|
|
47
|
+
isTransparent,
|
|
48
|
+
height,
|
|
49
|
+
symbol,
|
|
50
|
+
showIntervalTabs,
|
|
51
|
+
displayMode,
|
|
52
|
+
locale,
|
|
53
|
+
colorTheme,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
containerRef.current.appendChild(script)
|
|
57
|
+
|
|
58
|
+
return () => {
|
|
59
|
+
if (containerRef.current) {
|
|
60
|
+
// Remove initialization flag on unmount
|
|
61
|
+
delete containerRef.current.dataset.initialized
|
|
62
|
+
// Remove all scripts
|
|
63
|
+
const scripts = containerRef.current.querySelectorAll('script')
|
|
64
|
+
scripts.forEach((s) => s.remove())
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}, [symbol, interval, width, height, locale, colorTheme, isTransparent, showIntervalTabs, displayMode])
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div className="tradingview-widget-container w-full h-[500px]" ref={containerRef}>
|
|
71
|
+
<div className="tradingview-widget-container__widget h-full"></div>
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, memo } from 'react'
|
|
4
|
+
|
|
5
|
+
function TickerTape() {
|
|
6
|
+
const container = useRef<HTMLDivElement>(null)
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (!container.current) return
|
|
10
|
+
|
|
11
|
+
// Check if widget is already initialized using data attribute
|
|
12
|
+
if (container.current.dataset.initialized === 'true') {
|
|
13
|
+
return // Widget already initialized, skip
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Mark as initialized before adding script
|
|
17
|
+
container.current.dataset.initialized = 'true'
|
|
18
|
+
|
|
19
|
+
const script = document.createElement('script')
|
|
20
|
+
script.src = 'https://s3.tradingview.com/external-embedding/embed-widget-ticker-tape.js'
|
|
21
|
+
script.type = 'text/javascript'
|
|
22
|
+
script.async = true
|
|
23
|
+
script.innerHTML = JSON.stringify({
|
|
24
|
+
symbols: [
|
|
25
|
+
{ proName: 'FOREXCOM:SPXUSD', title: 'S&P 500' },
|
|
26
|
+
{ proName: 'FOREXCOM:NSXUSD', title: 'US 100' },
|
|
27
|
+
{ proName: 'FX_IDC:EURUSD', title: 'EUR/USD' },
|
|
28
|
+
{ proName: 'BITSTAMP:BTCUSD', title: 'Bitcoin' },
|
|
29
|
+
{ proName: 'BITSTAMP:ETHUSD', title: 'Ethereum' },
|
|
30
|
+
{ description: 'Tesla', proName: 'NASDAQ:TSLA' },
|
|
31
|
+
{ description: 'Apple', proName: 'NASDAQ:AAPL' },
|
|
32
|
+
{ description: 'Amazon', proName: 'NASDAQ:AMZN' },
|
|
33
|
+
{ description: 'NVIDIA', proName: 'NASDAQ:NVDA' },
|
|
34
|
+
{ description: 'Microsoft', proName: 'NASDAQ:MSFT' },
|
|
35
|
+
{ description: 'Google', proName: 'NASDAQ:GOOGL' },
|
|
36
|
+
{ description: 'Meta', proName: 'NASDAQ:META' },
|
|
37
|
+
],
|
|
38
|
+
showSymbolLogo: true,
|
|
39
|
+
isTransparent: false,
|
|
40
|
+
displayMode: 'adaptive',
|
|
41
|
+
colorTheme: 'dark',
|
|
42
|
+
locale: 'en',
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
container.current.appendChild(script)
|
|
46
|
+
|
|
47
|
+
// Cleanup function
|
|
48
|
+
return () => {
|
|
49
|
+
if (container.current) {
|
|
50
|
+
// Remove initialization flag on unmount
|
|
51
|
+
delete container.current.dataset.initialized
|
|
52
|
+
// Remove all scripts
|
|
53
|
+
const scripts = container.current.querySelectorAll('script')
|
|
54
|
+
scripts.forEach((s) => s.remove())
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}, [])
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="tradingview-widget-container w-full" ref={container}>
|
|
61
|
+
<div className="tradingview-widget-container__widget"></div>
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default memo(TickerTape)
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react'
|
|
4
|
+
import { TrendingUp, TrendingDown } from 'lucide-react'
|
|
5
|
+
|
|
6
|
+
export interface TradingPanelProps {
|
|
7
|
+
symbol: string
|
|
8
|
+
currentPrice: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function TradingPanel({ symbol, currentPrice }: TradingPanelProps) {
|
|
12
|
+
const [orderType, setOrderType] = useState<'buy' | 'sell'>('buy')
|
|
13
|
+
const [quantity, setQuantity] = useState('')
|
|
14
|
+
const [portfolio, setPortfolio] = useState<any>(null)
|
|
15
|
+
const [message, setMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null)
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
loadPortfolio()
|
|
19
|
+
}, [])
|
|
20
|
+
|
|
21
|
+
const loadPortfolio = () => {
|
|
22
|
+
const storedPortfolio = localStorage.getItem('begm_portfolio')
|
|
23
|
+
if (storedPortfolio) {
|
|
24
|
+
setPortfolio(JSON.parse(storedPortfolio))
|
|
25
|
+
} else {
|
|
26
|
+
const initialPortfolio = {
|
|
27
|
+
cash: 100000,
|
|
28
|
+
holdings: []
|
|
29
|
+
}
|
|
30
|
+
localStorage.setItem('begm_portfolio', JSON.stringify(initialPortfolio))
|
|
31
|
+
setPortfolio(initialPortfolio)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const executeTrade = () => {
|
|
36
|
+
if (!quantity || parseFloat(quantity) <= 0) {
|
|
37
|
+
setMessage({ type: 'error', text: 'Please enter a valid quantity' })
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const qty = parseFloat(quantity)
|
|
42
|
+
const total = qty * currentPrice
|
|
43
|
+
|
|
44
|
+
if (orderType === 'buy') {
|
|
45
|
+
// Check if user has enough cash
|
|
46
|
+
if (total > portfolio.cash) {
|
|
47
|
+
setMessage({ type: 'error', text: 'Insufficient funds' })
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Update portfolio
|
|
52
|
+
const existingHolding = portfolio.holdings.find((h: any) => h.symbol === symbol)
|
|
53
|
+
if (existingHolding) {
|
|
54
|
+
// Update existing holding
|
|
55
|
+
const newQuantity = existingHolding.quantity + qty
|
|
56
|
+
const newAveragePrice = ((existingHolding.averagePrice * existingHolding.quantity) + (currentPrice * qty)) / newQuantity
|
|
57
|
+
existingHolding.quantity = newQuantity
|
|
58
|
+
existingHolding.averagePrice = newAveragePrice
|
|
59
|
+
existingHolding.currentPrice = currentPrice
|
|
60
|
+
} else {
|
|
61
|
+
// Add new holding
|
|
62
|
+
portfolio.holdings.push({
|
|
63
|
+
symbol,
|
|
64
|
+
quantity: qty,
|
|
65
|
+
averagePrice: currentPrice,
|
|
66
|
+
currentPrice
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
portfolio.cash -= total
|
|
71
|
+
} else {
|
|
72
|
+
// Selling
|
|
73
|
+
const existingHolding = portfolio.holdings.find((h: any) => h.symbol === symbol)
|
|
74
|
+
|
|
75
|
+
if (!existingHolding || existingHolding.quantity < qty) {
|
|
76
|
+
setMessage({ type: 'error', text: 'Insufficient shares' })
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Update holding
|
|
81
|
+
existingHolding.quantity -= qty
|
|
82
|
+
existingHolding.currentPrice = currentPrice
|
|
83
|
+
|
|
84
|
+
// Remove holding if quantity is 0
|
|
85
|
+
if (existingHolding.quantity === 0) {
|
|
86
|
+
portfolio.holdings = portfolio.holdings.filter((h: any) => h.symbol !== symbol)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
portfolio.cash += total
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Save updated portfolio
|
|
93
|
+
localStorage.setItem('begm_portfolio', JSON.stringify(portfolio))
|
|
94
|
+
setPortfolio({ ...portfolio })
|
|
95
|
+
|
|
96
|
+
// Record order
|
|
97
|
+
const orders = JSON.parse(localStorage.getItem('begm_orders') || '[]')
|
|
98
|
+
orders.push({
|
|
99
|
+
id: `ORD-${Date.now()}`,
|
|
100
|
+
symbol,
|
|
101
|
+
type: orderType,
|
|
102
|
+
quantity: qty,
|
|
103
|
+
price: currentPrice,
|
|
104
|
+
total,
|
|
105
|
+
timestamp: new Date().toISOString(),
|
|
106
|
+
status: 'completed'
|
|
107
|
+
})
|
|
108
|
+
localStorage.setItem('begm_orders', JSON.stringify(orders))
|
|
109
|
+
|
|
110
|
+
setMessage({
|
|
111
|
+
type: 'success',
|
|
112
|
+
text: `Successfully ${orderType === 'buy' ? 'bought' : 'sold'} ${qty} shares of ${symbol}`
|
|
113
|
+
})
|
|
114
|
+
setQuantity('')
|
|
115
|
+
|
|
116
|
+
// Clear message after 3 seconds
|
|
117
|
+
setTimeout(() => setMessage(null), 3000)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const getHoldingInfo = () => {
|
|
121
|
+
if (!portfolio) return null
|
|
122
|
+
const holding = portfolio.holdings.find((h: any) => h.symbol === symbol)
|
|
123
|
+
return holding
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const holding = getHoldingInfo()
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div className="bg-white/5 border border-white/10 rounded-lg p-6 backdrop-blur-xl">
|
|
130
|
+
<h3 className="text-lg font-bold text-white mb-4">Trade {symbol}</h3>
|
|
131
|
+
|
|
132
|
+
{/* Order Type Selection */}
|
|
133
|
+
<div className="flex gap-2 mb-4">
|
|
134
|
+
<button
|
|
135
|
+
onClick={() => setOrderType('buy')}
|
|
136
|
+
className={`flex-1 py-2 rounded-lg font-medium transition-all ${
|
|
137
|
+
orderType === 'buy'
|
|
138
|
+
? 'bg-success text-white'
|
|
139
|
+
: 'bg-white/5 text-white/60 hover:bg-white/10'
|
|
140
|
+
}`}
|
|
141
|
+
>
|
|
142
|
+
<TrendingUp size={16} className="inline mr-2" />
|
|
143
|
+
Buy
|
|
144
|
+
</button>
|
|
145
|
+
<button
|
|
146
|
+
onClick={() => setOrderType('sell')}
|
|
147
|
+
className={`flex-1 py-2 rounded-lg font-medium transition-all ${
|
|
148
|
+
orderType === 'sell'
|
|
149
|
+
? 'bg-danger text-white'
|
|
150
|
+
: 'bg-white/5 text-white/60 hover:bg-white/10'
|
|
151
|
+
}`}
|
|
152
|
+
>
|
|
153
|
+
<TrendingDown size={16} className="inline mr-2" />
|
|
154
|
+
Sell
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
{/* Current Holdings Info */}
|
|
159
|
+
{holding && (
|
|
160
|
+
<div className="mb-4 p-3 bg-white/5 rounded-lg">
|
|
161
|
+
<p className="text-xs text-white/60 mb-1">Your Holdings</p>
|
|
162
|
+
<p className="text-sm text-white">
|
|
163
|
+
{holding.quantity} shares @ ${holding.averagePrice.toFixed(2)} avg
|
|
164
|
+
</p>
|
|
165
|
+
</div>
|
|
166
|
+
)}
|
|
167
|
+
|
|
168
|
+
{/* Price Info */}
|
|
169
|
+
<div className="mb-4 p-3 bg-white/5 rounded-lg">
|
|
170
|
+
<p className="text-xs text-white/60 mb-1">Current Price</p>
|
|
171
|
+
<p className="text-lg font-bold text-white">${currentPrice.toFixed(2)}</p>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
{/* Quantity Input */}
|
|
175
|
+
<div className="mb-4">
|
|
176
|
+
<label className="block text-sm font-medium text-white/80 mb-2">
|
|
177
|
+
Quantity (Shares)
|
|
178
|
+
</label>
|
|
179
|
+
<input
|
|
180
|
+
type="number"
|
|
181
|
+
value={quantity}
|
|
182
|
+
onChange={(e) => setQuantity(e.target.value)}
|
|
183
|
+
min="1"
|
|
184
|
+
step="1"
|
|
185
|
+
placeholder="Enter quantity"
|
|
186
|
+
className="w-full px-4 py-2 bg-white/5 border border-white/10 rounded-lg text-white placeholder-white/40 focus:outline-none focus:border-success"
|
|
187
|
+
/>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
{/* Order Total */}
|
|
191
|
+
{quantity && parseFloat(quantity) > 0 && (
|
|
192
|
+
<div className="mb-4 p-3 bg-white/5 rounded-lg">
|
|
193
|
+
<div className="flex justify-between items-center">
|
|
194
|
+
<span className="text-sm text-white/60">Estimated Total</span>
|
|
195
|
+
<span className="text-lg font-bold text-white">
|
|
196
|
+
${(parseFloat(quantity) * currentPrice).toFixed(2)}
|
|
197
|
+
</span>
|
|
198
|
+
</div>
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
|
|
202
|
+
{/* Available Cash */}
|
|
203
|
+
<div className="mb-4 p-3 bg-white/5 rounded-lg">
|
|
204
|
+
<div className="flex justify-between items-center">
|
|
205
|
+
<span className="text-sm text-white/60">Available Cash</span>
|
|
206
|
+
<span className="text-sm font-medium text-white">
|
|
207
|
+
${portfolio?.cash.toLocaleString() || '0'}
|
|
208
|
+
</span>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
{/* Message */}
|
|
213
|
+
{message && (
|
|
214
|
+
<div className={`mb-4 p-3 rounded-lg ${
|
|
215
|
+
message.type === 'success' ? 'bg-success/20 text-success' : 'bg-danger/20 text-danger'
|
|
216
|
+
}`}>
|
|
217
|
+
<p className="text-sm">{message.text}</p>
|
|
218
|
+
</div>
|
|
219
|
+
)}
|
|
220
|
+
|
|
221
|
+
{/* Execute Button */}
|
|
222
|
+
<button
|
|
223
|
+
onClick={executeTrade}
|
|
224
|
+
className={`w-full py-3 rounded-lg font-medium transition-all ${
|
|
225
|
+
orderType === 'buy'
|
|
226
|
+
? 'bg-success hover:bg-success/90 text-white'
|
|
227
|
+
: 'bg-danger hover:bg-danger/90 text-white'
|
|
228
|
+
}`}
|
|
229
|
+
>
|
|
230
|
+
{orderType === 'buy' ? 'Buy' : 'Sell'} {symbol}
|
|
231
|
+
</button>
|
|
232
|
+
|
|
233
|
+
<p className="text-xs text-white/40 text-center mt-4">
|
|
234
|
+
This is a demo account. No real money is at risk.
|
|
235
|
+
</p>
|
|
236
|
+
</div>
|
|
237
|
+
)
|
|
238
|
+
}
|