@hanzo/ui 5.1.8 → 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.
Files changed (272) hide show
  1. package/dist/3d/button.js +1 -0
  2. package/dist/3d/button.mjs +1 -0
  3. package/dist/3d/card.js +1 -0
  4. package/dist/3d/card.mjs +1 -0
  5. package/dist/3d/carousel.js +1 -0
  6. package/dist/3d/carousel.mjs +1 -0
  7. package/dist/3d/grid.js +1 -0
  8. package/dist/3d/grid.mjs +1 -0
  9. package/dist/3d/index.js +1 -0
  10. package/dist/3d/index.mjs +1 -0
  11. package/dist/3d/marquee.js +1 -0
  12. package/dist/3d/marquee.mjs +1 -0
  13. package/dist/3d/pin.js +1 -0
  14. package/dist/3d/pin.mjs +1 -0
  15. package/dist/animation/apple-cards-carousel.js +1 -0
  16. package/dist/animation/apple-cards-carousel.mjs +1 -0
  17. package/dist/animation/apple-hello-effect.js +1 -0
  18. package/dist/animation/apple-hello-effect.mjs +1 -0
  19. package/dist/animation/beam.js +1 -0
  20. package/dist/animation/beam.mjs +1 -0
  21. package/dist/animation/cursor.js +1 -0
  22. package/dist/animation/cursor.mjs +1 -0
  23. package/dist/animation/index.js +2 -0
  24. package/dist/animation/index.mjs +2 -0
  25. package/dist/animation/testimonials.js +1 -0
  26. package/dist/animation/testimonials.mjs +1 -0
  27. package/dist/animation/tooltip.js +1 -0
  28. package/dist/animation/tooltip.mjs +1 -0
  29. package/dist/avatar.js +1 -1
  30. package/dist/avatar.mjs +1 -1
  31. package/dist/chunk-266OC746.mjs +2 -0
  32. package/dist/chunk-2B7NONPA.mjs +1 -0
  33. package/dist/chunk-2SI3T6F4.mjs +1 -0
  34. package/dist/chunk-37TVHMP2.mjs +1 -0
  35. package/dist/chunk-4A27XTBF.js +12 -0
  36. package/dist/chunk-4CJ44NSE.js +1 -0
  37. package/dist/chunk-4SKGA2GA.mjs +1 -0
  38. package/dist/chunk-4TE2JWNG.js +1 -0
  39. package/dist/chunk-4XCQFT2C.mjs +1 -0
  40. package/dist/chunk-4YMLAU5Z.js +1 -0
  41. package/dist/chunk-5RACWNGV.mjs +1 -0
  42. package/dist/chunk-A3S2QAGG.mjs +3 -0
  43. package/dist/chunk-B3L4REN3.js +1 -0
  44. package/dist/chunk-B4RJSSWX.js +1 -0
  45. package/dist/chunk-B7LBF7R7.js +1 -0
  46. package/dist/chunk-BIWGFJPQ.mjs +1 -0
  47. package/dist/chunk-BOBL44QL.mjs +2 -0
  48. package/dist/chunk-BUMLLGES.mjs +1 -0
  49. package/dist/chunk-BYFLZ6DP.js +1 -0
  50. package/dist/chunk-C66RILAI.mjs +42 -0
  51. package/dist/chunk-CDBOLI7U.js +42 -0
  52. package/dist/chunk-CDO4BR6V.js +1 -0
  53. package/dist/chunk-CGUI5CEC.mjs +1 -0
  54. package/dist/chunk-CICZVOAY.js +1 -0
  55. package/dist/chunk-DB3WJTS4.js +1 -0
  56. package/dist/chunk-DKL2FM7J.mjs +1 -0
  57. package/dist/chunk-DNYROKHM.mjs +1 -0
  58. package/dist/chunk-DRLJ6Z7Q.js +1 -0
  59. package/dist/chunk-DYF5HRXR.mjs +1 -0
  60. package/dist/chunk-EATSKPYS.js +10 -0
  61. package/dist/chunk-EGN6SLB7.js +3 -0
  62. package/dist/chunk-EK5T4DQ2.js +2 -0
  63. package/dist/chunk-ELR6GOUX.mjs +1 -0
  64. package/dist/chunk-EQAPJD4H.mjs +1 -0
  65. package/dist/chunk-FCHJPQ73.mjs +1 -0
  66. package/dist/chunk-FE2O6776.js +1 -0
  67. package/dist/chunk-FIN2IX2D.mjs +1 -0
  68. package/dist/chunk-FQO25CNQ.mjs +1 -0
  69. package/dist/chunk-FX7VUERX.mjs +2 -0
  70. package/dist/chunk-G4X4J5EW.js +1 -0
  71. package/dist/chunk-GFWDGZO7.mjs +1 -0
  72. package/dist/chunk-GTFU3T2U.mjs +1 -0
  73. package/dist/chunk-GTUCZ5TW.js +1 -0
  74. package/dist/chunk-HEZV2WAY.js +2 -0
  75. package/dist/chunk-HIDGPSBX.js +1 -0
  76. package/dist/chunk-HLJAWNJK.js +1 -0
  77. package/dist/chunk-HM47SDFC.mjs +1 -0
  78. package/dist/chunk-HQGRRTIW.mjs +2 -0
  79. package/dist/chunk-J4DJZHOE.js +2 -0
  80. package/dist/chunk-J5KX3VTM.js +1 -0
  81. package/dist/chunk-JNNKXOA3.js +1 -0
  82. package/dist/chunk-JRWBF5SH.js +1 -0
  83. package/dist/chunk-JTTJLJON.js +2 -0
  84. package/dist/chunk-JZGTSHZ4.js +1 -0
  85. package/dist/chunk-LADFCKQD.mjs +1 -0
  86. package/dist/chunk-LHKPSY4L.js +1 -0
  87. package/dist/chunk-LMOX4GTY.js +1 -0
  88. package/dist/chunk-LYXX7LU2.js +6 -0
  89. package/dist/chunk-M4GZKMQS.mjs +6 -0
  90. package/dist/chunk-MJDVKTGX.mjs +1 -0
  91. package/dist/chunk-MPZRRATY.mjs +1 -0
  92. package/dist/chunk-MUI6SJLP.js +1 -0
  93. package/dist/chunk-NBXNOMXS.mjs +2 -0
  94. package/dist/chunk-NE7ICOQE.mjs +2 -0
  95. package/dist/chunk-NL5ZWDIJ.mjs +1 -0
  96. package/dist/chunk-NS6CEKHH.mjs +1 -0
  97. package/dist/chunk-O6RJT46G.js +1 -0
  98. package/dist/chunk-OC34KEWQ.mjs +1 -0
  99. package/dist/chunk-OL63HLUI.js +2 -0
  100. package/dist/chunk-OLTPOUB2.js +2 -0
  101. package/dist/chunk-OQOBF7QQ.js +1 -0
  102. package/dist/chunk-OY2LIJR2.mjs +2 -0
  103. package/dist/chunk-PE5GANLF.mjs +1 -0
  104. package/dist/chunk-PIDMTL36.mjs +1 -0
  105. package/dist/chunk-QGHPGSQL.mjs +2 -0
  106. package/dist/chunk-QLB43TFO.mjs +1 -0
  107. package/dist/chunk-R2JKVWNB.js +2 -0
  108. package/dist/chunk-RAT4U2JG.mjs +1 -0
  109. package/dist/chunk-RBURLSG7.js +2 -0
  110. package/dist/chunk-RO3D3W57.js +1 -0
  111. package/dist/chunk-RPOB4E3W.js +1 -0
  112. package/dist/chunk-RVPP46TI.js +2 -0
  113. package/dist/chunk-S2UB25HB.mjs +12 -0
  114. package/dist/chunk-SCYHSLR2.js +2 -0
  115. package/dist/chunk-SRPQZYB6.js +1 -0
  116. package/dist/chunk-TCTFHNND.js +1 -0
  117. package/dist/chunk-TG6V2JL2.js +1 -0
  118. package/dist/chunk-TGL6VBFN.js +1 -0
  119. package/dist/chunk-TNF5G2ZW.mjs +1 -0
  120. package/dist/chunk-TXND3LRQ.mjs +1 -0
  121. package/dist/chunk-U7CUKU4W.mjs +1 -0
  122. package/dist/chunk-UPRWI4ON.mjs +2 -0
  123. package/dist/chunk-V23X5ZD6.mjs +2 -0
  124. package/dist/chunk-VFFLOHVQ.mjs +1 -0
  125. package/dist/chunk-VHB7M646.mjs +10 -0
  126. package/dist/chunk-VJDDFZ7N.mjs +2 -0
  127. package/dist/chunk-VN2BKXHJ.js +1 -0
  128. package/dist/chunk-VT77IEYF.mjs +1 -0
  129. package/dist/chunk-VTJ6QUCR.js +1 -0
  130. package/dist/chunk-VUIJAFJC.js +1 -0
  131. package/dist/chunk-WN767YS2.mjs +2 -0
  132. package/dist/chunk-WREKV6XQ.mjs +1 -0
  133. package/dist/chunk-XBDQSMTI.js +2 -0
  134. package/dist/chunk-XFK7RTKE.js +1 -0
  135. package/dist/chunk-XJTWK37Q.mjs +2 -0
  136. package/dist/chunk-XMYCJ2B5.js +1 -0
  137. package/dist/chunk-XOGRJZXX.mjs +1 -0
  138. package/dist/chunk-XOQCRR5Q.js +2 -0
  139. package/dist/chunk-Y6SIRKG3.js +2 -0
  140. package/dist/chunk-YLY2B6UX.mjs +1 -0
  141. package/dist/chunk-YQUF7KPG.mjs +1 -0
  142. package/dist/chunk-Z3DNBRF4.js +1 -0
  143. package/dist/chunk-ZA3JIBDG.js +1 -0
  144. package/dist/chunk-ZDOIGP2L.js +1 -0
  145. package/dist/code/block.js +1 -0
  146. package/dist/code/block.mjs +1 -0
  147. package/dist/code/compare.js +1 -0
  148. package/dist/code/compare.mjs +1 -0
  149. package/dist/code/diff.js +1 -0
  150. package/dist/code/diff.mjs +1 -0
  151. package/dist/code/editor.js +1 -0
  152. package/dist/code/editor.mjs +1 -0
  153. package/dist/code/explorer.js +1 -0
  154. package/dist/code/explorer.mjs +1 -0
  155. package/dist/code/index.js +1 -0
  156. package/dist/code/index.mjs +1 -0
  157. package/dist/code/preview.js +1 -0
  158. package/dist/code/preview.mjs +1 -0
  159. package/dist/code/snippet.js +1 -0
  160. package/dist/code/snippet.mjs +1 -0
  161. package/dist/code/tabs.js +1 -0
  162. package/dist/code/tabs.mjs +1 -0
  163. package/dist/code/terminal.js +1 -0
  164. package/dist/code/terminal.mjs +1 -0
  165. package/dist/device/index.js +1 -0
  166. package/dist/device/index.mjs +0 -0
  167. package/dist/dock/basic.js +1 -0
  168. package/dist/dock/basic.mjs +1 -0
  169. package/dist/dock/index.js +1 -0
  170. package/dist/dock/index.mjs +1 -0
  171. package/dist/dock/limelight-nav.js +1 -0
  172. package/dist/dock/limelight-nav.mjs +1 -0
  173. package/dist/dock/macos.js +1 -0
  174. package/dist/dock/macos.mjs +1 -0
  175. package/dist/dock/menu.js +1 -0
  176. package/dist/dock/menu.mjs +1 -0
  177. package/dist/dock/message.js +1 -0
  178. package/dist/dock/message.mjs +1 -0
  179. package/dist/finance/AdvancedChart.js +1 -0
  180. package/dist/finance/AdvancedChart.mjs +1 -0
  181. package/dist/finance/CompanyProfile.js +1 -0
  182. package/dist/finance/CompanyProfile.mjs +1 -0
  183. package/dist/finance/CryptoScreener.js +1 -0
  184. package/dist/finance/CryptoScreener.mjs +1 -0
  185. package/dist/finance/Financials.js +1 -0
  186. package/dist/finance/Financials.mjs +1 -0
  187. package/dist/finance/ForexScreener.js +1 -0
  188. package/dist/finance/ForexScreener.mjs +1 -0
  189. package/dist/finance/MarketOverview.js +1 -0
  190. package/dist/finance/MarketOverview.mjs +1 -0
  191. package/dist/finance/NewsTimeline.js +1 -0
  192. package/dist/finance/NewsTimeline.mjs +1 -0
  193. package/dist/finance/OrderEntry.js +1 -0
  194. package/dist/finance/OrderEntry.mjs +1 -0
  195. package/dist/finance/OrdersHistory.js +1 -0
  196. package/dist/finance/OrdersHistory.mjs +1 -0
  197. package/dist/finance/PositionsList.js +1 -0
  198. package/dist/finance/PositionsList.mjs +1 -0
  199. package/dist/finance/StockScreener.js +1 -0
  200. package/dist/finance/StockScreener.mjs +1 -0
  201. package/dist/finance/SymbolInfo.js +1 -0
  202. package/dist/finance/SymbolInfo.mjs +1 -0
  203. package/dist/finance/TechnicalAnalysis.js +1 -0
  204. package/dist/finance/TechnicalAnalysis.mjs +1 -0
  205. package/dist/finance/TickerTape.js +1 -0
  206. package/dist/finance/TickerTape.mjs +1 -0
  207. package/dist/finance/TradingPanel.js +1 -0
  208. package/dist/finance/TradingPanel.mjs +1 -0
  209. package/dist/finance/index.js +1 -0
  210. package/dist/finance/index.mjs +1 -0
  211. package/dist/form/index.js +1 -0
  212. package/dist/form/index.mjs +1 -0
  213. package/dist/index.js +1 -1
  214. package/dist/index.mjs +1 -1
  215. package/dist/navigation/index.js +1 -0
  216. package/dist/navigation/index.mjs +1 -0
  217. package/dist/pattern/grid.js +1 -0
  218. package/dist/pattern/grid.mjs +1 -0
  219. package/dist/pattern/index.js +1 -0
  220. package/dist/pattern/index.mjs +1 -0
  221. package/dist/primitives/index.js +1 -1
  222. package/dist/primitives/index.mjs +1 -1
  223. package/dist/project/gantt.js +1 -0
  224. package/dist/project/gantt.mjs +1 -0
  225. package/dist/project/index.js +1 -0
  226. package/dist/project/index.mjs +1 -0
  227. package/dist/project/kanban.js +1 -0
  228. package/dist/project/kanban.mjs +1 -0
  229. package/dist/project/list.js +1 -0
  230. package/dist/project/list.mjs +1 -0
  231. package/dist/ui/announcement.js +1 -0
  232. package/dist/ui/announcement.mjs +1 -0
  233. package/dist/ui/avatar-group.js +1 -0
  234. package/dist/ui/avatar-group.mjs +1 -0
  235. package/dist/ui/banner.js +1 -0
  236. package/dist/ui/banner.mjs +1 -0
  237. package/dist/ui/cursor.js +1 -0
  238. package/dist/ui/cursor.mjs +1 -0
  239. package/dist/ui/index.js +1 -0
  240. package/dist/ui/index.mjs +1 -0
  241. package/dist/ui/marquee.js +1 -0
  242. package/dist/ui/marquee.mjs +1 -0
  243. package/dist/ui/pill.js +1 -0
  244. package/dist/ui/pill.mjs +1 -0
  245. package/dist/ui/spinner.js +1 -0
  246. package/dist/ui/spinner.mjs +1 -0
  247. package/dist/ui/tags.js +1 -0
  248. package/dist/ui/tags.mjs +1 -0
  249. package/dist/ui/ticker.js +1 -0
  250. package/dist/ui/ticker.mjs +1 -0
  251. package/finance/README.md +164 -0
  252. package/finance/components/AdvancedChart.tsx +58 -0
  253. package/finance/components/CompanyProfile.tsx +65 -0
  254. package/finance/components/CryptoScreener.tsx +55 -0
  255. package/finance/components/Financials.tsx +71 -0
  256. package/finance/components/ForexScreener.tsx +56 -0
  257. package/finance/components/MarketOverview.tsx +114 -0
  258. package/finance/components/NewsTimeline.tsx +54 -0
  259. package/finance/components/OrderEntry.tsx +157 -0
  260. package/finance/components/OrdersHistory.tsx +103 -0
  261. package/finance/components/PositionsList.tsx +85 -0
  262. package/finance/components/StockScreener.tsx +56 -0
  263. package/finance/components/SymbolInfo.tsx +62 -0
  264. package/finance/components/TechnicalAnalysis.tsx +74 -0
  265. package/finance/components/TickerTape.tsx +66 -0
  266. package/finance/components/TradingPanel.tsx +238 -0
  267. package/finance/components/index.ts +40 -0
  268. package/finance/index.ts +23 -0
  269. package/package.json +129 -3
  270. package/style/theme-provider.tsx +1 -1
  271. package/dist/chunk-BBXIHLTX.mjs +0 -1
  272. 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
+ }