1+ import { useMemo , useState } from 'react' ;
2+
13import { useParams } from 'react-router-dom' ;
24import styled , { css } from 'styled-components' ;
35
@@ -6,33 +8,224 @@ import { TradeLayouts } from '@/constants/layout';
68import breakpoints from '@/styles/breakpoints' ;
79import { layoutMixins } from '@/styles/layoutMixins' ;
810
11+ import { IconName } from '@/components/Icon' ;
12+ import { Output , OutputType } from '@/components/Output' ;
913import { SpotTvChart } from '@/views/charts/TradingView/SpotTvChart' ;
1014
1115import { useAppSelector } from '@/state/appTypes' ;
1216import { getSelectedTradeLayout } from '@/state/layoutSelectors' ;
1317
18+ import { SpotHeader } from './SpotHeader' ;
19+ import { type SpotHoldingRow } from './SpotHoldingsTable' ;
20+ import { SpotHorizontalPanel } from './SpotHorizontalPanel' ;
21+ import { SpotTokenInfo } from './SpotTokenInfo' ;
1422import { SpotTradeForm } from './SpotTradeForm' ;
23+ import { SpotMarketToken } from './types' ;
24+
25+ function generateDummyHoldings ( count : number ) : SpotHoldingRow [ ] {
26+ const rows : SpotHoldingRow [ ] = [ ] ;
27+ for ( let i = 0 ; i < count ; i += 1 ) {
28+ const tokenSymbol = `${ i } ASSET` ;
29+ const tokenName = `${ [ ...tokenSymbol ] . reverse ( ) . join ( '' ) } ` ;
30+ const holdingsAmount = Math . round ( 1000 + Math . random ( ) * 2_000_000 ) ;
31+ const avgPrice = 0.0001 + Math . random ( ) * 5 ;
32+ const holdingsUsd = Math . round ( holdingsAmount * avgPrice ) ;
33+ const boughtAmount = holdingsAmount ;
34+ const boughtUsd = holdingsUsd ;
35+ const soldAmount = Math . round ( Math . random ( ) * 10_000 ) ;
36+ const soldUsd = Math . round ( soldAmount * avgPrice ) ;
37+ const pnlUsd = Math . round ( ( Math . random ( ) - 0.5 ) * 10_000 ) ;
38+
39+ rows . push ( {
40+ tokenAddress : tokenSymbol ,
41+ tokenSymbol,
42+ tokenName,
43+ holdingsAmount,
44+ holdingsUsd,
45+ boughtAmount,
46+ boughtUsd,
47+ soldAmount,
48+ soldUsd,
49+ pnlUsd,
50+ } ) ;
51+ }
52+ return rows ;
53+ }
54+
55+ const DUMMY_TOKENS : SpotMarketToken [ ] = [
56+ {
57+ tokenAddress : 'So11111111111111111111111111111111111111112' ,
58+ name : 'Solana' ,
59+ symbol : 'SOL' ,
60+ logoUrl : 'https://cryptologos.cc/logos/solana-sol-logo.png' ,
61+ volume24hUsd : 224_400_000 ,
62+ priceUsd : 151.23 ,
63+ marketCapUsd : 68_000_000_000 ,
64+ change24hPercent : 2.35 ,
65+ markPriceUsd : 151.2 ,
66+ fdvUsd : 70_000_000_000 ,
67+ liquidityUsd : 1_000_000_000 ,
68+ circulatingSupply : 450_000_000 ,
69+ totalSupply : 560_000_000 ,
70+ percentChange24h : 2.35 ,
71+ buys24hUsd : 120_000_000 ,
72+ sells24hUsd : - 104_400_000 ,
73+ } ,
74+ {
75+ tokenAddress : 'FARTxLVqm9ezNvQ8V4E8w9FVBYRHGpJzp2cXm7pump' ,
76+ name : 'Fartcoin' ,
77+ symbol : 'FARTCOIN' ,
78+ logoUrl : 'https://cryptologos.cc/logos/fartcoin-fart-logo.png' ,
79+ volume24hUsd : 124_300_000 ,
80+ priceUsd : 1.53 ,
81+ marketCapUsd : 1_530_000_000 ,
82+ change24hPercent : 12.35 ,
83+ markPriceUsd : 1.54 ,
84+ fdvUsd : 1_600_000_000 ,
85+ liquidityUsd : 30_000_000 ,
86+ circulatingSupply : 1_000_000_000 ,
87+ totalSupply : 1_600_000_000 ,
88+ percentChange24h : 12.35 ,
89+ buys24hUsd : 70_000_000 ,
90+ sells24hUsd : - 54_300_000 ,
91+ } ,
92+ {
93+ tokenAddress : 'WIFgzYxgkMtFGzGYAzm72rnWC9eFsEhSUvBdtpump' ,
94+ name : 'dogwifhat' ,
95+ symbol : 'WIF' ,
96+ logoUrl : 'https://cryptologos.cc/logos/dogwifhat-wif-logo.png' ,
97+ volume24hUsd : 111_200_000 ,
98+ priceUsd : 0.652 ,
99+ marketCapUsd : 290_000_000 ,
100+ change24hPercent : - 3.35 ,
101+ markPriceUsd : 0.651 ,
102+ fdvUsd : 300_000_000 ,
103+ liquidityUsd : 20_000_000 ,
104+ circulatingSupply : 445_000_000 ,
105+ totalSupply : 460_000_000 ,
106+ percentChange24h : - 3.35 ,
107+ buys24hUsd : 50_000_000 ,
108+ sells24hUsd : - 61_200_000 ,
109+ } ,
110+ {
111+ tokenAddress : 'BONKxYxgkMtFGzGYAzm72rnWC9eFsEhSUvBdtbonk' ,
112+ name : 'Bonk' ,
113+ symbol : 'BONK' ,
114+ logoUrl : 'https://cryptologos.cc/logos/bonk-bonk-logo.png' ,
115+ volume24hUsd : 80_000_000 ,
116+ priceUsd : 0.000025 ,
117+ marketCapUsd : 1_500_000_000 ,
118+ change24hPercent : 8.5 ,
119+ markPriceUsd : 0.0000251 ,
120+ fdvUsd : 2_000_000_000 ,
121+ liquidityUsd : 10_000_000 ,
122+ circulatingSupply : 60_000_000_000_000 ,
123+ totalSupply : 100_000_000_000_000 ,
124+ percentChange24h : 8.5 ,
125+ buys24hUsd : 45_000_000 ,
126+ sells24hUsd : - 35_000_000 ,
127+ } ,
128+ ] ;
15129
16130const SpotPage = ( ) => {
17131 const { symbol } = useParams < { symbol : string } > ( ) ;
18132 const tradeLayout = useAppSelector ( getSelectedTradeLayout ) ;
19133
134+ const [ isHorizontalOpen , setIsHorizontalOpen ] = useState ( true ) ;
135+
136+ const dummyHoldings : SpotHoldingRow [ ] = useMemo ( ( ) => generateDummyHoldings ( 50 ) , [ ] ) ;
137+
138+ const handleTokenSelect = ( ) => {
139+ // Navigate
140+ } ;
141+
142+ const handlePositionSelect = ( ) => {
143+ // Navigate
144+ } ;
145+
146+ const handleTokenSearchChange = ( ) => {
147+ // Query search API
148+ } ;
149+
150+ const handlePositionSell = ( ) => {
151+ // Sell dialog or navigate
152+ } ;
153+
20154 return (
21- < $SpotLayout tradeLayout = { tradeLayout } >
155+ < $SpotLayout tradeLayout = { tradeLayout } isHorizontalOpen = { isHorizontalOpen } >
22156 < header tw = "[grid-area:Top]" >
23- < div tw = "p-1" > Spot Market Selector (Coming Soon)</ div >
157+ < SpotHeader
158+ currentToken = { DUMMY_TOKENS [ 1 ] ! }
159+ searchResults = { DUMMY_TOKENS }
160+ onTokenSelect = { handleTokenSelect }
161+ onSearchTextChange = { handleTokenSearchChange }
162+ />
24163 </ header >
25164
26- < $GridSection gridArea = "Side" tw = "p-1" >
165+ < $GridSection gridArea = "Side" >
27166 < SpotTradeForm />
167+ < SpotTokenInfo
168+ links = { [
169+ { icon : IconName . Earth , url : '' } ,
170+ { icon : IconName . File , url : '' } ,
171+ { icon : IconName . CoinMarketCap , url : '' } ,
172+ { icon : IconName . SocialX , url : '' } ,
173+ ] }
174+ contractAddress = "WIFgzYxgkMtFGzGYAzm72rnWC9eFsEhSUvBdtpump"
175+ createdAt = { Date . now ( ) - 21 * 24 * 60 * 60 * 1000 }
176+ items = { [
177+ {
178+ key : 'holders' ,
179+ iconName : IconName . Positions ,
180+ label : 'Holders' ,
181+ value : < Output type = { OutputType . CompactNumber } value = { 123123 } /> ,
182+ } ,
183+ {
184+ key : 'top10' ,
185+ iconName : IconName . Position ,
186+ label : 'Top 10' ,
187+ value : < Output type = { OutputType . Percent } value = { 0.0424 } /> ,
188+ } ,
189+ {
190+ key : 'devHolding' ,
191+ iconName : IconName . Gear ,
192+ label : 'Dev Holding' ,
193+ value : < Output type = { OutputType . Percent } value = { 0.0123 } /> ,
194+ } ,
195+ {
196+ key : 'snipers' ,
197+ iconName : IconName . Viewfinder ,
198+ label : 'Snipers' ,
199+ value : < Output type = { OutputType . Percent } value = { 0.0124 } /> ,
200+ } ,
201+ {
202+ key : 'bundlers' ,
203+ iconName : IconName . Shield ,
204+ label : 'Bundlers' ,
205+ value : < Output type = { OutputType . Percent } value = { 0.0424 } /> ,
206+ } ,
207+ {
208+ key : 'insiders' ,
209+ iconName : IconName . Warning ,
210+ label : 'Insiders' ,
211+ value : < Output type = { OutputType . Percent } value = { 0.01 } /> ,
212+ } ,
213+ ] }
214+ />
28215 </ $GridSection >
29216
30217 < $GridSection gridArea = "Inner" >
31218 < SpotTvChart symbol = { symbol ! } />
32219 </ $GridSection >
33220
34221 < $GridSection gridArea = "Horizontal" >
35- < div tw = "p-1" > Spot Horizontal Panel (Coming Soon)</ div >
222+ < SpotHorizontalPanel
223+ data = { dummyHoldings }
224+ isOpen = { isHorizontalOpen }
225+ setIsOpen = { setIsHorizontalOpen }
226+ onRowAction = { handlePositionSelect }
227+ onSellAction = { handlePositionSell }
228+ />
36229 </ $GridSection >
37230 </ $SpotLayout >
38231 ) ;
@@ -42,6 +235,7 @@ export default SpotPage;
42235
43236const $SpotLayout = styled . article < {
44237 tradeLayout : TradeLayouts ;
238+ isHorizontalOpen : boolean ;
45239} > `
46240 /* prettier-ignore */
47241 --layout-default:
@@ -57,10 +251,8 @@ const $SpotLayout = styled.article<{
57251 'Horizontal Side' 300px
58252 / 1fr 400px;
59253
60- // Props/defaults
61254 --layout: var(--layout-default);
62255
63- // Variants
64256 @media ${ breakpoints . desktopMedium } {
65257 --layout: var(--layout-default-desktopMedium);
66258 }
@@ -76,7 +268,16 @@ const $SpotLayout = styled.article<{
76268 ` ,
77269 } ) [ tradeLayout ] }
78270
79- // Rules
271+ ${ ( { isHorizontalOpen } ) =>
272+ ! isHorizontalOpen &&
273+ css `
274+ --layout-default : 'Top Top' auto 'Inner Side' minmax (0 , 1fr ) 'Horizontal Side'
275+ var (--tabs-height ) / 1fr 400px ;
276+
277+ --layout-default-desktopMedium : 'Top Side' auto 'Inner Side' minmax (0 , 1fr ) 'Horizontal Side'
278+ var (--tabs-height ) / 1fr 400px ;
279+ ` }
280+
80281 width: 0;
81282 min-width: 100%;
82283 height: 0;
@@ -103,4 +304,5 @@ const $SpotLayout = styled.article<{
103304
104305const $GridSection = styled . section < { gridArea : string } > `
105306 grid-area: ${ ( { gridArea } ) => gridArea } ;
307+ ${ layoutMixins . withOuterAndInnerBorders }
106308` ;
0 commit comments