Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Market Overview #2494

Merged
merged 37 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4f3c06e
feat: mockup ui marketoverview
viet-nv Apr 22, 2024
98b9705
Merge branch 'main' of github.com:KyberNetwork/kyberswap-interface in…
viet-nv May 8, 2024
6f02595
temp
viet-nv May 9, 2024
ea36ebb
feat: market overview
viet-nv May 13, 2024
26fde7a
feat: handle favorite
viet-nv May 13, 2024
cc08569
feat: handle popup
viet-nv May 13, 2024
ab5d295
add gainers, losers
viet-nv May 13, 2024
ca88a21
fix: page size
viet-nv May 13, 2024
37a946b
fix: minor
viet-nv May 14, 2024
d816dae
feat: mobile view
viet-nv May 14, 2024
f83586b
feat: mobile view
viet-nv May 15, 2024
274997a
fix: ui issue when token name too long
viet-nv May 15, 2024
9f1e0f8
fix: add/remove favorite ux
viet-nv May 15, 2024
4c6b18e
fix: sort token price
viet-nv May 16, 2024
6f9c085
fix: sort mobile
viet-nv May 17, 2024
8d895b0
feat: auto refresh price
viet-nv May 22, 2024
b2afed6
show qoute token
viet-nv May 23, 2024
a7b89fb
feat: update price change
viet-nv May 23, 2024
9707bc2
update favorite logic
viet-nv May 23, 2024
b9b7a5c
fix: mobile price change
viet-nv May 23, 2024
7581728
fix: mc 0 to --
viet-nv May 24, 2024
834de19
fix: price change 1h on desktop
viet-nv May 24, 2024
d0f2e97
Merge branch 'main' of github.com:KyberNetwork/kyberswap-interface in…
viet-nv Jun 18, 2024
7db8b59
tmp
viet-nv Jun 24, 2024
220235d
feat: separate buy/sell price
viet-nv Jun 25, 2024
e2fc027
feat: update ui
viet-nv Jul 12, 2024
1b7cfe8
Merge branch 'main' of github.com:KyberNetwork/kyberswap-interface in…
viet-nv Jul 12, 2024
b2b03b3
Merge branch 'main' of github.com:KyberNetwork/kyberswap-interface in…
viet-nv Jul 15, 2024
7f3e01f
fix: UI issue
viet-nv Jul 15, 2024
bc4a2e9
fix: UI issue
viet-nv Jul 15, 2024
e752bee
Merge branch 'main' of github.com:KyberNetwork/kyberswap-interface in…
viet-nv Aug 6, 2024
0cb2bd4
Merge branch 'main' of github.com:KyberNetwork/kyberswap-interface in…
viet-nv Aug 19, 2024
aa53a4c
Update .env.production
viet-nv Aug 20, 2024
f1f9eac
Update .env.production
viet-nv Aug 21, 2024
4fcb3ae
Merge branch 'main' into feat/market-overview
viet-nv Aug 21, 2024
9f26cac
feat: add tooltip to chainId
viet-nv Aug 22, 2024
e3e951b
chore: enable all chain
viet-nv Aug 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/components/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import AboutNavGroup from './groups/AboutNavGroup'
import CampaignNavGroup from './groups/CampaignNavGroup'
import KyberDAONavGroup from './groups/KyberDaoGroup'
import SwapNavGroup from './groups/SwapNavGroup'
import { StyledNavExternalLink } from './styleds'
import { StyledNavExternalLink, StyledNavLink } from './styleds'

const HeaderFrame = styled.div<{ hide?: boolean }>`
height: ${({ hide }) => (hide ? 0 : undefined)};
Expand Down Expand Up @@ -208,6 +208,8 @@ export default function Header() {
{!isPartnerSwap && (
<HeaderLinks>
<SwapNavGroup />

<StyledNavLink to={`${APP_PATHS.MARKET_OVERVIEW}`}>Market</StyledNavLink>
<KyberDAONavGroup />
<CampaignNavGroup />
<StyledNavExternalLink target="_blank" href={AGGREGATOR_ANALYTICS_URL}>
Expand Down
2 changes: 1 addition & 1 deletion src/components/SearchInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const SearchContainer = styled.div`
background: ${({ theme }) => theme.background};
border-radius: 999px;
width: 320px;
font-size: 12px;
font-size: 14px;
padding: 8px 12px;
display: flex;
align-items: center;
Expand Down
1 change: 1 addition & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ export const APP_PATHS = {

DEPRECATED_NOTI_CENTER: '/notification-center/overview',
ELASTIC_SNAPSHOT: '/elastic-snapshot',
MARKET_OVERVIEW: '/market-overview',

AGGREGATOR_CAMPAIGN: '/campaigns/aggregator',
LIMIT_ORDER_CAMPAIGN: '/campaigns/limit-order',
Expand Down
2 changes: 2 additions & 0 deletions src/pages/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const Logout = lazy(() => import('./Oauth/Logout'))
const Consent = lazy(() => import('./Oauth/Consent'))

const ElasticSnapshot = lazy(() => import('./ElasticSnapshot'))
const MarketOverview = lazy(() => import('./MarketOverview'))

// test page for swap only through elastic
const ElasticSwap = lazy(() => import('./ElasticSwap'))
Expand Down Expand Up @@ -306,6 +307,7 @@ export default function App() {
<Route path={APP_PATHS.IAM_CONSENT} element={<Consent />} />

<Route path={APP_PATHS.ELASTIC_SNAPSHOT} element={<ElasticSnapshot />} />
<Route path={APP_PATHS.MARKET_OVERVIEW} element={<MarketOverview />} />

<Route path={APP_PATHS.AGGREGATOR_CAMPAIGN} element={<Campaign />} />
<Route path={APP_PATHS.LIMIT_ORDER_CAMPAIGN} element={<Campaign />} />
Expand Down
329 changes: 329 additions & 0 deletions src/pages/MarketOverview/DetailModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
import { ChainId, WETH } from '@kyberswap/ks-sdk-core'
import { useEffect, useState } from 'react'
import { Star, X } from 'react-feather'
import { useMedia, usePreviousDistinct } from 'react-use'
import { Box, Flex, Text } from 'rebass'
import { AssetToken, useGetQuoteByChainQuery } from 'services/marketOverview'

import { ButtonEmpty, ButtonOutlined } from 'components/Button'
import CopyHelper from 'components/Copy'
import Modal from 'components/Modal'
import { ETHER_ADDRESS } from 'constants/index'
import { MAINNET_NETWORKS } from 'constants/networks'
import { NativeCurrencies } from 'constants/tokens'
import { NETWORKS_INFO } from 'hooks/useChainsConfig'
import useTheme from 'hooks/useTheme'
import { MEDIA_WIDTHS } from 'theme'
import { shortenAddress } from 'utils'
import { formatDisplayNumber } from 'utils/numbers'

import { ContentChangable, TabItem } from './styles'

// () => setShowTokenId(null)
export default function DetailModal({
tokenToShow,
onDismiss,
toggleFavorite,
latestPrices,
}: {
tokenToShow: AssetToken
onDismiss: () => void
toggleFavorite: (tk: AssetToken) => void
latestPrices: {
current: undefined | { data: { [chainId: string]: { [address: string]: { PriceBuy: number; PriceSell: number } } } }
}
}) {
const theme = useTheme()
const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`)
const { data: quoteData } = useGetQuoteByChainQuery()
const [selectedPrice, setSelectedPrice] = useState<'buy' | 'sell'>('buy')

return (
<Modal isOpen={!!tokenToShow} onDismiss={onDismiss} width="100%" maxWidth="600px">
{tokenToShow ? (
<Flex width="100%" flexDirection="column" padding={upToSmall ? '1rem' : '2rem'}>
<Flex justifyContent="space-between" sx={{ gap: '4px' }}>
<Flex alignItems="center" sx={{ gap: '6px' }} flex="1">
<img
src={tokenToShow.logoURL || 'https://i.imgur.com/b3I8QRs.jpeg'}
width="24px"
height="24px"
alt=""
style={{
borderRadius: '50%',
}}
/>
<Text fontSize={16} minWidth="max-content">
{tokenToShow.symbol} {}
</Text>
<Text
fontSize={14}
color={theme.subText}
sx={{ whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }}
>
{tokenToShow.name}
</Text>

<Star
size={16}
color={tokenToShow.isFavorite ? theme.yellow1 : theme.subText}
role="button"
cursor="pointer"
fill={tokenToShow.isFavorite ? theme.yellow1 : 'none'}
onClick={() => toggleFavorite(tokenToShow)}
style={{ minWidth: '16px' }}
/>
</Flex>

<ButtonEmpty onClick={onDismiss} width="fit-content" style={{ padding: 0 }}>
<X color={theme.text} />
</ButtonEmpty>
</Flex>

<Box
sx={{
background: '#ffffff20',
padding: '0.75rem 1rem',
display: 'grid',
gridTemplateColumns: upToSmall ? '1fr 1fr' : '1fr 1fr 1fr 1fr',
gap: upToSmall ? '0.75rem' : '1.5rem',
height: 'fit-content',
borderRadius: '1rem',
marginTop: '1.25rem',
}}
>
<div>
<Text fontSize={12} color={theme.subText}>
Market Cap
</Text>
<Text marginTop="4px">
{tokenToShow.marketCap
? formatDisplayNumber(tokenToShow.marketCap, { style: 'currency', fractionDigits: 2 })
: '--'}
</Text>
</div>
<div>
<Text fontSize={12} color={theme.subText}>
24h Volume
</Text>
<Text marginTop="4px">
{tokenToShow.volume24h
? formatDisplayNumber(tokenToShow.volume24h, { style: 'currency', fractionDigits: 2 })
: '--'}
</Text>
</div>

<div>
<Text fontSize={12} color={theme.subText}>
All Time Low
</Text>
<Text marginTop="4px">
{formatDisplayNumber(tokenToShow.allTimeLow, { style: 'currency', fractionDigits: 2 })}
</Text>
</div>
<div>
<Text fontSize={12} color={theme.subText}>
All Time High
</Text>
<Text marginTop="4px">
{formatDisplayNumber(tokenToShow.allTimeHigh, { style: 'currency', fractionDigits: 2 })}
</Text>
</div>
</Box>
{!upToSmall ? (
<Box
sx={{ display: 'grid', gridTemplateColumns: `1fr ${upToSmall ? '100px 80px' : '0.75fr 0.75fr 0.75fr'}` }}
marginTop="1.5rem"
marginBottom="8px"
>
<div />
<Text fontSize={12} color={theme.subText} textAlign="right">
Buy Price
</Text>
<Text fontSize={12} color={theme.subText} textAlign="right">
Sell Price
</Text>
<div />
</Box>
) : (
<>
<Flex alignItems="center" marginY="8px" justifyContent="flex-end" sx={{ gap: '12px' }}>
<Text fontSize={12} color={theme.subText} textAlign="right">
On-chain Price
</Text>
<Flex
sx={{ border: `1px solid ${theme.border}`, background: theme.buttonBlack, borderRadius: '999px' }}
padding="1px"
width="fit-content"
>
<TabItem
active={selectedPrice === 'buy'}
onClick={() => {
setSelectedPrice('buy')
}}
>
Buy
</TabItem>

<TabItem
active={selectedPrice === 'sell'}
onClick={() => {
setSelectedPrice('sell')
}}
>
Sell
</TabItem>
</Flex>
</Flex>
</>
)}

{tokenToShow.tokens
.filter(token => MAINNET_NETWORKS.includes(+token.chainId))
.sort((a, b) => b.priceBuy - a.priceBuy)
.map(token => {
const quoteSymbol = quoteData?.data?.onchainPrice?.usdQuoteTokenByChainId?.[token.chainId]?.symbol
const address =
token.address.toLowerCase() === ETHER_ADDRESS.toLowerCase()
? WETH[token.chainId as ChainId].address
: token.address

const price = token
? latestPrices.current?.data?.[token.chainId]?.[token.address]?.[
selectedPrice === 'buy' ? 'PriceBuy' : 'PriceSell'
] || (selectedPrice === 'buy' ? token.priceBuy : token.priceSell)
: ''

return (
<Box
key={token.chainId}
sx={{
display: 'grid',
gridTemplateColumns: `1fr ${upToSmall ? '100px 80px' : '0.75fr 0.75fr 0.75fr'}`,
}}
marginBottom="1rem"
alignItems="center"
>
<Flex sx={{ gap: '12px' }} alignItems="center">
<Box sx={{ position: 'relative' }} width="32px">
<img
src={tokenToShow.logoURL || 'https://i.imgur.com/b3I8QRs.jpeg'}
width="32px"
height="32px"
alt=""
style={{
borderRadius: '50%',
}}
/>

<img
src={NETWORKS_INFO[token.chainId as ChainId].icon}
alt=""
width="16px"
height="16px"
style={{ position: 'absolute', right: '-8px', bottom: 0 }}
/>
</Box>
<div style={{ flex: 1, overflow: 'hidden' }}>
<Flex alignItems="center" fontSize={16}>
{tokenToShow.symbol}{' '}
{quoteSymbol && (
<Text
as="span"
color={theme.subText}
fontSize={14}
sx={{
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
}}
>
/{quoteSymbol}
</Text>
)}
</Flex>
<Text color={theme.subText} display="flex" marginTop="2px" fontSize="12px">
{shortenAddress(1, address)}
<CopyHelper toCopy={address} />
</Text>
</div>
</Flex>

{upToSmall ? (
<Text textAlign="right">
<Price price={+price} />
</Text>
) : (
<>
<Text textAlign="right">
<Price price={+token.priceBuy} />
</Text>

<Text textAlign="right">
<Price price={+token.priceSell} />
</Text>
</>
)}

<Flex sx={{ gap: '12px' }} alignItems="center" justifyContent="flex-end">
<ButtonOutlined
color={theme.primary}
style={{
width: 'fit-content',
padding: '4px 12px',
}}
onClick={() => {
window.open(
`/swap/${NETWORKS_INFO[token.chainId as ChainId].route}?inputCurrency=${
NativeCurrencies[token.chainId as ChainId].symbol
}&outputCurrency=${token.address}`,
'_blank',
)
}}
>
Swap
</ButtonOutlined>
</Flex>
</Box>
)
})}
</Flex>
) : null}
</Modal>
)
}

export const Price = ({ price }: { price: number }) => {
const [animate, setAnimate] = useState(false)
useEffect(() => {
setAnimate(true)
setTimeout(() => setAnimate(false), 1200)
}, [price])

const lastPrice = usePreviousDistinct(price)

return (
<ContentChangable animate={!!lastPrice && animate} up={!!lastPrice && price - lastPrice >= 0}>
{!price ? '--' : formatDisplayNumber(price, { fractionDigits: 2, significantDigits: 7 })}
</ContentChangable>
)
}

export const PriceChange = ({ priceChange }: { priceChange: number | undefined }) => {
const [animate, setAnimate] = useState(false)
useEffect(() => {
setAnimate(true)
setTimeout(() => setAnimate(false), 1200)
}, [priceChange])

const lastPriceChange = usePreviousDistinct(priceChange)

return (
<ContentChangable
animate={!!lastPriceChange && animate}
up={!!lastPriceChange && !!priceChange && priceChange - lastPriceChange >= 0}
>
{!priceChange ? '--' : priceChange.toFixed(2) + '%'}
</ContentChangable>
)
}
Loading
Loading