From 6cf5cf8e20cf1670d0784bd6622d135f87dd7843 Mon Sep 17 00:00:00 2001 From: XiaoYhun Date: Tue, 31 Oct 2023 14:58:22 +0700 Subject: [PATCH] Update new tab kyberai (#2324) * add liquidity profile tab * add markets table * add api for liquidity market * update get data from cgk * update api * update UI * update UI * update UI * hide perpetual chart on cgk api * update fundingrate color * show action button on both dex and cex table * increase limit to 50 * update api param and icon * update pagination and missing icon * improve mobile UI * revert env --- src/assets/svg/sprite.svg | 37 +- src/components/Button/index.tsx | 15 +- src/components/Select/index.tsx | 102 ++++-- src/constants/index.ts | 1 + .../components/TokenFilter/index.tsx | 1 + .../TrueSightV2/components/chart/index.tsx | 55 +++ src/pages/TrueSightV2/components/index.tsx | 24 +- .../components/table/LiquidityMarkets.tsx | 341 ++++++++++++++++++ .../TrueSightV2/components/table/index.tsx | 20 +- src/pages/TrueSightV2/constants/index.tsx | 2 + .../hooks/useCoinmarketcapData.tsx | 31 ++ .../TrueSightV2/pages/LiquidityAnalysis.tsx | 44 +++ src/pages/TrueSightV2/pages/SingleToken.tsx | 12 +- src/pages/TrueSightV2/types/index.tsx | 5 +- src/pages/TrueSightV2/utils/index.tsx | 33 +- src/services/coingecko.ts | 8 +- src/state/index.ts | 3 + src/state/user/reducer.ts | 2 + 18 files changed, 653 insertions(+), 83 deletions(-) create mode 100644 src/pages/TrueSightV2/components/table/LiquidityMarkets.tsx create mode 100644 src/pages/TrueSightV2/hooks/useCoinmarketcapData.tsx create mode 100644 src/pages/TrueSightV2/pages/LiquidityAnalysis.tsx diff --git a/src/assets/svg/sprite.svg b/src/assets/svg/sprite.svg index 05b0b093e5..02ef249a30 100644 --- a/src/assets/svg/sprite.svg +++ b/src/assets/svg/sprite.svg @@ -225,7 +225,7 @@ @@ -299,14 +299,7 @@ - + + + + + + + + + + + + + + + ` cursor: pointer; appearance: none; padding: 2px; - background-color: transparent; display: flex; align-items: center; outline: none; border-radius: 50%; border: none; - color: unset; transition: all 0.1s; + background-color: ${({ $color }) => $color + '32' || 'transparent'}; + color: ${({ $color }) => $color || 'unset'}; :hover { - background-color: ${({ theme }) => theme.subText + '20'}; + background-color: ${({ theme, $color }) => ($color ? $color + '20' : theme.subText + '20')}; } :active { - background-color: ${({ theme }) => theme.subText + '10'}; + background-color: ${({ theme, $color }) => ($color ? $color + '10' : theme.subText + '10')}; transform: translateY(2px); } ` @@ -417,10 +417,11 @@ const StyledButtonAction = styled(RebassButton)` export const ButtonAction = ({ onClick, children, + color, ...rest -}: { onClick?: () => void; children: ReactNode } & ButtonProps) => { +}: { onClick?: () => void; children: ReactNode } & ButtonProps & { color?: string }) => { return ( - + {children} ) diff --git a/src/components/Select/index.tsx b/src/components/Select/index.tsx index 105c00edae..9b840f55c5 100644 --- a/src/components/Select/index.tsx +++ b/src/components/Select/index.tsx @@ -1,3 +1,4 @@ +import { t } from '@lingui/macro' import { Placement } from '@popperjs/core' import { Portal } from '@reach/portal' import { AnimatePresence, motion } from 'framer-motion' @@ -5,6 +6,7 @@ import { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react' import { usePopper } from 'react-popper' import styled from 'styled-components' +import Icon from 'components/Icons/Icon' import { Z_INDEXS } from 'constants/styles' import { useOnClickOutside } from 'hooks/useOnClickOutside' @@ -27,17 +29,18 @@ const SelectWrapper = styled.div` ` const SelectMenu = styled(motion.div)` + padding: 8px; border-radius: 16px; overflow: hidden; filter: drop-shadow(0px 4px 12px rgba(0, 0, 0, 0.36)); z-index: 2; background: ${({ theme }) => theme.tabActive}; - padding: 10px 0px; width: max-content; ` const Option = styled.div<{ $selected: boolean }>` - padding: 10px 18px; + padding: 8px; + border-radius: 8px; cursor: pointer; font-size: 12px; color: ${({ theme }) => theme.subText}; @@ -55,6 +58,33 @@ const SelectedWrap = styled.div` flex: 1; user-select: none; ` + +const SearchWrapper = styled.div` + position: relative; + display: flex; + justify-content: center; + align-items: center; + border-radius: 8px; + background-color: ${({ theme }) => theme.buttonGray}; + margin-bottom: 8px; + transition: all 0.1s ease; + transition-property: background-color, color; + color: ${({ theme }) => theme.subText}; + :hover { + background-color: ${({ theme }) => theme.buttonBlack}; + color: ${({ theme }) => theme.text}; + } + :focus-within { + background-color: ${({ theme }) => theme.buttonBlack}; + color: ${({ theme }) => theme.text}; + } + input { + width: 100%; + padding-inline-start: 40px; + line-height: 32px; + color: ${({ theme }) => theme.text}; + } +` export type SelectOption = { value?: string | number; label: ReactNode; onSelect?: () => void } const getOptionValue = (option: SelectOption | undefined) => { @@ -81,6 +111,7 @@ function Select({ arrowColor, dropdownRender, onHideMenu, + withSearch, placement = 'bottom', }: { value?: string | number @@ -96,10 +127,12 @@ function Select({ forceMenuPlacementTop?: boolean arrowColor?: string placement?: string + withSearch?: boolean onHideMenu?: () => void // hide without changes }) { const [selected, setSelected] = useState(getOptionValue(options?.[0])) const [showMenu, setShowMenu] = useState(false) + const [searchValue, setSearchValue] = useState('') const [menuPlacementTop] = useState(forceMenuPlacementTop) useEffect(() => { @@ -116,30 +149,36 @@ function Select({ const selectedInfo = options.find(item => getOptionValue(item) === selected) const renderMenu = () => { - return options.map(item => { - const value = getOptionValue(item) - const onClick = (e: React.MouseEvent) => { - e.stopPropagation() - e.preventDefault() - setShowMenu(false) - if (item.onSelect) item.onSelect?.() - else { - setSelected(value) - onChange?.(value) + return options + .filter(item => { + if (!withSearch) return true + return item.label?.toString().toLowerCase().includes(searchValue.toLowerCase()) + }) + .map(item => { + const value = getOptionValue(item) + const onClick = (e: React.MouseEvent) => { + e.stopPropagation() + e.preventDefault() + setShowMenu(false) + setSearchValue('') + if (item.onSelect) item.onSelect?.() + else { + setSelected(value) + onChange?.(value) + } } - } - return ( - - ) - }) + return ( + + ) + }) } const [popperElement, setPopperElement] = useState(null) @@ -180,6 +219,19 @@ function Select({ transition={{ duration: 0.1 }} style={menuStyle} > + {withSearch && ( + e.stopPropagation()}> + + + + setSearchValue(e.target.value)} + /> + + )}
{dropdownRender ? dropdownRender(renderMenu()) : renderMenu()}
diff --git a/src/constants/index.ts b/src/constants/index.ts index 6984f92ed5..fa340712f7 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -322,6 +322,7 @@ export const ICON_IDS = [ 'alarm', 'on-chain', 'technical-analysis', + 'liquidity-analysis', 'news', 'arrow', 'chart', diff --git a/src/pages/TrueSightV2/components/TokenFilter/index.tsx b/src/pages/TrueSightV2/components/TokenFilter/index.tsx index 32e8884f21..bed8ee8d99 100644 --- a/src/pages/TrueSightV2/components/TokenFilter/index.tsx +++ b/src/pages/TrueSightV2/components/TokenFilter/index.tsx @@ -251,6 +251,7 @@ export default function TokenFilter({ onChange={value => onChangeFilter(queryKey, value)} optionStyle={{ fontSize: '14px' }} menuStyle={menuStyle} + withSearch={queryKey === 'categories'} /> ))} {isWatchlistTab && ( diff --git a/src/pages/TrueSightV2/components/chart/index.tsx b/src/pages/TrueSightV2/components/chart/index.tsx index e92ed98df3..2da1ef1f44 100644 --- a/src/pages/TrueSightV2/components/chart/index.tsx +++ b/src/pages/TrueSightV2/components/chart/index.tsx @@ -2814,3 +2814,58 @@ export const Prochart = ({ ) } + +/* IN DEVELOPMENT */ +export const LiquidityProfile = () => { + const theme = useTheme() + + const { state, dispatch } = useChartStatesContext(KYBERAI_CHART_ID.LIQUIDITY_PROFILE, { + showOptions: ['showPriceImpact', 'showBuy', 'showSell'], + noData: true, + }) + + const showPriceImpact = state?.showOptions?.includes('showPriceImpact') + const showBuy = state?.showOptions?.includes('showBuy') + const showSell = state?.showOptions?.includes('showSell') + const above768 = useMedia(`(min-width: ${MEDIA_WIDTHS.upToSmall}px)`) + + return ( + + + + dispatch({ type: CHART_STATES_ACTION_TYPE.TOGGLE_OPTION, payload: { option: 'showPriceImpact' } }) + } + /> + dispatch({ type: CHART_STATES_ACTION_TYPE.TOGGLE_OPTION, payload: { option: 'showBuy' } })} + /> + dispatch({ type: CHART_STATES_ACTION_TYPE.TOGGLE_OPTION, payload: { option: 'showSell' } })} + /> + + + + + + {/* IN DEVELOPMENT */} + + + + ) +} diff --git a/src/pages/TrueSightV2/components/index.tsx b/src/pages/TrueSightV2/components/index.tsx index fb1589a0f7..2259916d83 100644 --- a/src/pages/TrueSightV2/components/index.tsx +++ b/src/pages/TrueSightV2/components/index.tsx @@ -147,7 +147,7 @@ export const SectionWrapper = ({ const { data: token } = useKyberAIAssetOverview() const ref = useRef(null) const above768 = useMedia(`(min-width:${MEDIA_WIDTHS.upToSmall}px)`) - const [showText, setShowText] = useState(above768 ? true : false) + const [showText, setShowText] = useState(false) const [showShareModal, setShowShareModal] = useState(false) const [isTextExceeded, setIsTexExceeded] = useState(false) const [fullscreenMode, setFullscreenMode] = useState(false) @@ -155,13 +155,15 @@ export const SectionWrapper = ({ const descriptionRef = useRef(null) useLayoutEffect(() => { - setIsTexExceeded( - (description && - descriptionRef.current && - descriptionRef.current?.clientWidth <= descriptionRef.current?.scrollWidth) || - false, - ) - }, [description]) + if ( + description && + descriptionRef.current && + descriptionRef.current.clientWidth < descriptionRef.current.scrollWidth + ) { + setIsTexExceeded(true) + above768 && setShowText(true) + } + }, [description, descriptionRef, above768]) const docsLink = activeTab === ChartTab.Second && !!docsLinks[1] ? docsLinks[1] : docsLinks[0] @@ -261,10 +263,9 @@ export const SectionWrapper = ({ fontSize="14px" color={theme.primary} width="fit-content" - style={{ cursor: 'pointer', flexBasis: 'fit-content', whiteSpace: 'nowrap' }} + style={{ cursor: 'pointer', flexBasis: 'fit-content', whiteSpace: 'nowrap', marginLeft: '4px' }} onClick={() => setShowText(prev => !prev)} > - {' '} Hide )} @@ -365,10 +366,9 @@ export const SectionWrapper = ({ fontSize="12px" color={theme.primary} width="fit-content" - style={{ cursor: 'pointer', flexBasis: 'fit-content', whiteSpace: 'nowrap' }} + style={{ cursor: 'pointer', flexBasis: 'fit-content', whiteSpace: 'nowrap', marginLeft: '4px' }} onClick={() => setShowText(prev => !prev)} > - {' '} Hide )} diff --git a/src/pages/TrueSightV2/components/table/LiquidityMarkets.tsx b/src/pages/TrueSightV2/components/table/LiquidityMarkets.tsx new file mode 100644 index 0000000000..21064a7acd --- /dev/null +++ b/src/pages/TrueSightV2/components/table/LiquidityMarkets.tsx @@ -0,0 +1,341 @@ +import { t } from '@lingui/macro' +import { CSSProperties, useEffect, useMemo, useState } from 'react' +import { useMedia } from 'react-use' +import { Text } from 'rebass' +import { useGetLiquidityMarketsQuery as useGetLiquidityMarketsCoingecko } from 'services/coingecko' +import styled, { DefaultTheme, css } from 'styled-components' + +import { ButtonAction, ButtonPrimary } from 'components/Button' +import Column from 'components/Column' +import Icon from 'components/Icons/Icon' +import Pagination from 'components/Pagination' +import Row, { RowBetween, RowFit } from 'components/Row' +import useCoingeckoAPI from 'hooks/useCoingeckoAPI' +import { MIXPANEL_TYPE, useMixpanelKyberAI } from 'hooks/useMixpanel' +import useTheme from 'hooks/useTheme' +import { useGetLiquidityMarketsQuery as useGetLiquidityMarketsCoinmarketcap } from 'pages/TrueSightV2/hooks/useCoinmarketcapData' +import useKyberAIAssetOverview from 'pages/TrueSightV2/hooks/useKyberAIAssetOverview' +import { ChartTab } from 'pages/TrueSightV2/types' +import { colorFundingRateText, formatShortNum, formatTokenPrice, navigateToSwapPage } from 'pages/TrueSightV2/utils' +import { MEDIA_WIDTHS } from 'theme' + +import { LoadingHandleWrapper } from '.' + +const TableTab = styled.div<{ active?: boolean }>` + font-weight: 500; + font-size: 16px; + line-height: 24px; + padding: 12px 16px; + border-bottom: 2px solid transparent; + cursor: pointer; + :hover { + filter: brightness(1.2); + } + ${({ active, theme }) => + active + ? css` + background-color: ${theme.primary + '40'}; + color: ${theme.primary}; + border-color: ${theme.primary}; + ` + : css` + color: ${theme.subText}; + background-color: ${theme.background}; + `} + + ${({ theme }) => theme.mediaWidth.upToSmall` + font-size: 12px; + line-height:16px; + padding: 12px; + white-space: nowrap; + `} +` + +enum LIQUIDITY_MARKETS_TYPE { + COINMARKETCAP, + COINGECKO, +} + +const useLiquidityMarketsData = (activeTab: ChartTab, type?: LIQUIDITY_MARKETS_TYPE) => { + const { data: assetOverview } = useKyberAIAssetOverview() + const { data, isFetching: cmcFetching } = useGetLiquidityMarketsCoinmarketcap( + { + id: assetOverview?.cmcId, + centerType: activeTab === ChartTab.First ? 'dex' : activeTab === ChartTab.Second ? 'cex' : 'all', + category: activeTab === ChartTab.Third ? 'perpetual' : 'spot', + }, + { skip: !assetOverview?.cmcId && type !== LIQUIDITY_MARKETS_TYPE.COINMARKETCAP }, + ) + + const marketPairs = data?.data?.marketPairs || [] + + const coingeckoAPI = useCoingeckoAPI() + + const { data: cgkData, isFetching: cgkFetching } = useGetLiquidityMarketsCoingecko( + { coingeckoAPI, id: assetOverview?.cgkId }, + { skip: !assetOverview?.cgkId && type !== LIQUIDITY_MARKETS_TYPE.COINGECKO }, + ) + return { + cmcData: marketPairs, + cgkData: cgkData?.tickers || [], + isFetching: type === LIQUIDITY_MARKETS_TYPE.COINMARKETCAP ? cmcFetching : cgkFetching, + hasData: type === LIQUIDITY_MARKETS_TYPE.COINMARKETCAP ? !!marketPairs?.length : !!cgkData?.tickers?.length, + } +} + +const useRenderLiquidityMarkets = (activeTab: ChartTab, type?: LIQUIDITY_MARKETS_TYPE) => { + const tabs: Array<{ title: string; tabId: ChartTab }> = useMemo(() => { + if (type === LIQUIDITY_MARKETS_TYPE.COINMARKETCAP) { + return [ + { title: t`Decentralized Exchanges`, tabId: ChartTab.First }, + { title: t`Centralized Exchanges`, tabId: ChartTab.Second }, + { title: t`Perpetual Markets`, tabId: ChartTab.Third }, + ] + } + if (type === LIQUIDITY_MARKETS_TYPE.COINGECKO) { + return [{ title: t`Spot Markets`, tabId: ChartTab.First }] + } + return [] + }, [type]) + + const headers: Array<{ title: string; style?: CSSProperties }> = useMemo(() => { + if (type === LIQUIDITY_MARKETS_TYPE.COINMARKETCAP) { + if (activeTab === ChartTab.First) { + return [ + { title: '#' }, + { title: t`Exchange` }, + { title: t`Token pair` }, + { title: t`Current price` }, + { title: t`24h volume` }, + ] + } + if (activeTab === ChartTab.Second) { + return [ + { title: '#' }, + { title: t`Exchange` }, + { title: t`Token pair` }, + { title: t`Current price` }, + { title: t`24h volume` }, + { title: t`Action`, style: { textAlign: 'right' } }, + ] + } + if (activeTab === ChartTab.Third) { + return [ + { title: '#' }, + { title: t`Exchange` }, + { title: t`Token pair` }, + { title: t`Current price` }, + { title: t`24h volume` }, + { title: t`Funding rate` }, + ] + } + } + if (type === LIQUIDITY_MARKETS_TYPE.COINGECKO) { + return [ + { title: '#' }, + { title: t`Exchange` }, + { title: t`Token pair` }, + { title: t`Current price` }, + { title: t`24h volume` }, + { title: t`Action`, style: { textAlign: 'right' } }, + ] + } + return [] + }, [type, activeTab]) + + const renderCMCRow = (item: any, index: number, theme: DefaultTheme) => ( + + + {index + 1} + + + + exchange logo + {item.exchangeName} + + + + {item.marketPair} + + + ${formatTokenPrice(item.price)} + + + ${formatShortNum(item.volumeUsd)} + + {activeTab === ChartTab.Second && ( + + {item.marketUrl && ( + + + + + + )} + + )} + {activeTab === ChartTab.Third && ( + + + + {item.fundingRate ? (item.fundingRate * 100).toFixed(2) + '%' : '--'} + + + + )} + + ) + + const renderCGKRow = (item: any, index: number, theme: DefaultTheme) => ( + + + {index + 1} + + + + exchange logo + {item.market.name} + + + + + {(item.base.startsWith('0X') ? item.coin_id : item.base) + + '/' + + (item.target.startsWith('0X') ? item.target_coin_id : item.target)} + + + + ${formatTokenPrice(item.converted_last.usd)} + + + ${formatShortNum(item.converted_volume.usd)} + + + + + + + + + + ) + + return { + tabs, + headers, + renderCMCRow, + renderCGKRow, + } +} + +const PAGE_SIZE = 10 + +export default function LiquidityMarkets() { + const theme = useTheme() + const mixpanelHandler = useMixpanelKyberAI() + const { data: assetOverview, chain, address } = useKyberAIAssetOverview() + const [type, setType] = useState() + const [activeTab, setActiveTab] = useState(ChartTab.First) + const [page, setPage] = useState(1) + const above768 = useMedia(`(min-width:${MEDIA_WIDTHS.upToSmall}px)`) + + const { cmcData, cgkData, isFetching, hasData } = useLiquidityMarketsData(activeTab, type) + const { tabs, headers, renderCMCRow, renderCGKRow } = useRenderLiquidityMarkets(activeTab, type) + + useEffect(() => { + if (assetOverview) { + setActiveTab(ChartTab.First) + if (assetOverview.cmcId) { + setType(LIQUIDITY_MARKETS_TYPE.COINMARKETCAP) + } else if (assetOverview.cgkId) { + setType(LIQUIDITY_MARKETS_TYPE.COINGECKO) + } + } + }, [assetOverview]) + + return ( + <> + + + + {tabs.map(tab => ( + setActiveTab(tab.tabId)}> + {tab.title} + + ))} + + {type === LIQUIDITY_MARKETS_TYPE.COINMARKETCAP && activeTab === ChartTab.First && ( + { + mixpanelHandler(MIXPANEL_TYPE.KYBERAI_EXPLORING_SWAP_TOKEN_CLICK, { + token_name: assetOverview?.symbol?.toUpperCase(), + network: chain, + }) + navigateToSwapPage({ address, chain }) + }} + style={{ marginRight: '16px', fontSize: above768 ? '16px' : '12px' }} + flexShrink={0} + > + + + Swap {assetOverview?.symbol?.toUpperCase()} + + + )} + + + + + {new Array(headers.length).fill(0).map((_, index) => ( + + ))} + + + + {headers.map((item, index) => ( + + {item.title} + + ))} + + + + {type === LIQUIDITY_MARKETS_TYPE.COINMARKETCAP + ? cmcData.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE).map((item: any, index: number) => { + return renderCMCRow(item, index + (page - 1) * PAGE_SIZE, theme) + }) + : cgkData.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE).map((item: any, index: number) => { + return renderCGKRow(item, index + (page - 1) * PAGE_SIZE, theme) + })} + + + + + + ) +} diff --git a/src/pages/TrueSightV2/components/table/index.tsx b/src/pages/TrueSightV2/components/table/index.tsx index 53daa66af3..7c980a060d 100644 --- a/src/pages/TrueSightV2/components/table/index.tsx +++ b/src/pages/TrueSightV2/components/table/index.tsx @@ -2,7 +2,7 @@ import { Trans, t } from '@lingui/macro' import dayjs from 'dayjs' import { BigNumber } from 'ethers' import { formatUnits } from 'ethers/lib/utils' -import { ReactNode, useCallback, useContext, useMemo, useRef, useState } from 'react' +import { CSSProperties, ReactNode, useCallback, useContext, useMemo, useRef, useState } from 'react' import { isMobile } from 'react-device-detect' import { Info } from 'react-feather' import Skeleton, { SkeletonTheme } from 'react-loading-skeleton' @@ -49,7 +49,6 @@ import WatchlistButton from '../WatchlistButton' const TableWrapper = styled.div` overflow-x: scroll; border-radius: 6px; - ${({ theme }) => theme.mediaWidth.upToSmall` border-radius: 0; margin: -16px; @@ -62,6 +61,7 @@ const Table = styled.table` font-size: 12px; line-height: 16px; font-weight: 500; + white-space: nowrap; color: ${({ theme }) => theme.subText}; text-transform: uppercase; tr { @@ -90,6 +90,7 @@ const Table = styled.table` tr{ td, th{ padding: 12px 16px; + font-size: 12px; } } `} @@ -124,23 +125,29 @@ const StyledLoadingWrapper = styled.div` width: 100%; ` -const LoadingHandleWrapper = ({ +export const LoadingHandleWrapper = ({ isLoading, + isFetching, hasData, children, height, + minHeight, + style, }: { isLoading: boolean + isFetching?: boolean hasData: boolean children: ReactNode height?: string + minHeight?: string + style?: CSSProperties }) => { return ( - - + +
{!hasData ? ( - + {isLoading ? ( ) : ( @@ -171,7 +178,6 @@ export const Top10HoldersTable = () => { - {/* */} diff --git a/src/pages/TrueSightV2/constants/index.tsx b/src/pages/TrueSightV2/constants/index.tsx index 12245d8671..66f2e130e7 100644 --- a/src/pages/TrueSightV2/constants/index.tsx +++ b/src/pages/TrueSightV2/constants/index.tsx @@ -122,6 +122,8 @@ export enum KYBERAI_CHART_ID { HOLDER_PIE_CHART = 'holdersPieChart', PRICE_CHART = 'priceChart', LIQUID_ON_CEX = 'liquidOnCEX', + LIQUIDITY_PROFILE = 'liquidityProfile', + MARKETS = 'markets', } export const Z_INDEX_KYBER_AI = { diff --git a/src/pages/TrueSightV2/hooks/useCoinmarketcapData.tsx b/src/pages/TrueSightV2/hooks/useCoinmarketcapData.tsx new file mode 100644 index 0000000000..76eb84f050 --- /dev/null +++ b/src/pages/TrueSightV2/hooks/useCoinmarketcapData.tsx @@ -0,0 +1,31 @@ +import { createApi } from '@reduxjs/toolkit/dist/query/react' +import baseQueryOauth from 'services/baseQueryOauth' + +import { BFF_API } from 'constants/env' + +const coinmarketcapApi = createApi({ + reducerPath: 'coinmarketcapApi', + baseQuery: baseQueryOauth({ + baseUrl: `${BFF_API}/v1/coinmarketcap`, + }), + endpoints: builder => ({ + getLiquidityMarkets: builder.query({ + query: ({ id, category, centerType }) => ({ + url: `/data-api/v3/cryptocurrency/market-pairs/latest`, + params: { + id: id, + start: 1, + limit: 100, + category: category || 'spot', + centerType: centerType || 'all', + sort: 'volume_24h_strict', + direction: 'desc', + spotUntracked: 'true', + }, + }), + }), + }), +}) + +export const { useGetLiquidityMarketsQuery } = coinmarketcapApi +export default coinmarketcapApi diff --git a/src/pages/TrueSightV2/pages/LiquidityAnalysis.tsx b/src/pages/TrueSightV2/pages/LiquidityAnalysis.tsx new file mode 100644 index 0000000000..aee59e45ce --- /dev/null +++ b/src/pages/TrueSightV2/pages/LiquidityAnalysis.tsx @@ -0,0 +1,44 @@ +import { t } from '@lingui/macro' +import styled from 'styled-components' + +import { useTokenAnalysisSettings } from 'state/user/hooks' + +import { SectionWrapper } from '../components' +import LiquidityMarkets from '../components/table/LiquidityMarkets' +import { KYBERAI_CHART_ID } from '../constants' + +const Wrapper = styled.div` + padding: 20px 0; + width: 100%; +` + +export default function LiquidityAnalysis() { + const tokenAnalysisSettings = useTokenAnalysisSettings() + return ( + + {/* ----In development----- + } + docsLinks={[]} + > + + */} + + + + + ) +} diff --git a/src/pages/TrueSightV2/pages/SingleToken.tsx b/src/pages/TrueSightV2/pages/SingleToken.tsx index f77a89fdd2..8f8fd01128 100644 --- a/src/pages/TrueSightV2/pages/SingleToken.tsx +++ b/src/pages/TrueSightV2/pages/SingleToken.tsx @@ -33,6 +33,7 @@ import useChartStatesReducer, { ChartStatesContext } from '../hooks/useChartStat import useKyberAIAssetOverview from '../hooks/useKyberAIAssetOverview' import { DiscoverTokenTab, IAssetOverview } from '../types' import { navigateToSwapPage } from '../utils' +import LiquidityAnalysis from './LiquidityAnalysis' import OnChainAnalysis from './OnChainAnalysis' import TechnicalAnalysis from './TechnicalAnalysis' @@ -110,6 +111,7 @@ const TagWrapper = styled.div` gap: 8px; margin-bottom: 24px; overflow-x: scroll; + flex-wrap: wrap; ` const TabButton = styled.div<{ active?: boolean }>` @@ -509,9 +511,7 @@ export default function SingleToken() { const above768 = useMedia(`(min-width:${MEDIA_WIDTHS.upToSmall}px)`) const { assetId } = useParams() const [currentTab, setCurrentTab] = useState(DiscoverTokenTab.TechnicalAnalysis) - const { data: token, isLoading, chain: chainParam, address: addressParam } = useKyberAIAssetOverview() - const [viewAllTag, setViewAllTag] = useState(false) useEffect(() => { @@ -607,6 +607,7 @@ export default function SingleToken() { { [DiscoverTokenTab.OnChainAnalysis]: 'on-chain' as const, [DiscoverTokenTab.TechnicalAnalysis]: 'technical-analysis' as const, + [DiscoverTokenTab.LiquidityAnalysis]: 'liquidity-analysis' as const, }[tab] } size={20} @@ -620,8 +621,11 @@ export default function SingleToken() { - {currentTab === DiscoverTokenTab.TechnicalAnalysis && } - {currentTab === DiscoverTokenTab.OnChainAnalysis && } +
+ {currentTab === DiscoverTokenTab.TechnicalAnalysis && } + {currentTab === DiscoverTokenTab.OnChainAnalysis && } + {currentTab === DiscoverTokenTab.LiquidityAnalysis && } +
} - isWatched: boolean + cmcId: string + cgkId: string } export interface ITokenList { @@ -87,7 +88,6 @@ export interface ITokenList { prevKyberScore: number kyberScoreTag: string marketCap: number - isWatched: boolean cexNetflow24H: number cexNetflow3D: number discoveredOn?: number @@ -213,6 +213,7 @@ export interface ISRLevel { export enum DiscoverTokenTab { TechnicalAnalysis = 'Technical Analysis', OnChainAnalysis = 'On-Chain Analysis', + LiquidityAnalysis = 'Liquidity Analysis', } export enum ChartTab { diff --git a/src/pages/TrueSightV2/utils/index.tsx b/src/pages/TrueSightV2/utils/index.tsx index 55733e8df2..cc63ae2d0d 100644 --- a/src/pages/TrueSightV2/utils/index.tsx +++ b/src/pages/TrueSightV2/utils/index.tsx @@ -12,35 +12,36 @@ import { KYBERSCORE_TAG_TYPE, NETWORK_TO_CHAINID } from '../constants' export const calculateValueToColor = (value: number, theme: DefaultTheme) => { if (value === 0) return theme.subText - if (value < 17) { - return theme.red + + if (value > 83) { + return theme.primary } - if (value < 34) { - return '#FFA7C3' + if (value > 67) { + return '#8DE1C7' } - if (value < 68) { + if (value > 33) { return theme.text } - if (value < 84) { - return '#8DE1C7' + if (value > 16) { + return '#FFA7C3' } - return theme.primary + return theme.red } export const getTypeByKyberScore = (value: number): KYBERSCORE_TAG_TYPE => { - if (value < 17) { - return KYBERSCORE_TAG_TYPE.VERY_BEARISH + if (value > 83) { + return KYBERSCORE_TAG_TYPE.VERY_BULLISH } - if (value < 34) { - return KYBERSCORE_TAG_TYPE.BEARISH + if (value > 67) { + return KYBERSCORE_TAG_TYPE.BULLISH } - if (value < 68) { + if (value > 33) { return KYBERSCORE_TAG_TYPE.NEUTRAL } - if (value < 84) { - return KYBERSCORE_TAG_TYPE.BULLISH + if (value > 16) { + return KYBERSCORE_TAG_TYPE.BEARISH } - return KYBERSCORE_TAG_TYPE.VERY_BULLISH + return KYBERSCORE_TAG_TYPE.VERY_BEARISH } export const formatShortNum = (num: number, fixed = 1): string => { diff --git a/src/services/coingecko.ts b/src/services/coingecko.ts index 9cfa91356d..30ef41c7fe 100644 --- a/src/services/coingecko.ts +++ b/src/services/coingecko.ts @@ -38,6 +38,12 @@ const coingeckoApi = createApi({ authentication: true, }), }), + getLiquidityMarkets: builder.query({ + query: ({ coingeckoAPI, id }) => ({ + url: `${coingeckoAPI}/coins/${id}/tickers?include_exchange_logo=true`, + authentication: true, + }), + }), getSecurityTokenInfo: builder.query({ query: ({ chainId, address }) => ({ url: `https://api.gopluslabs.io/api/v1/token_security/${chainId}?contract_addresses=${address}`, @@ -48,6 +54,6 @@ const coingeckoApi = createApi({ }) // todo danh (not for now) move basic chart api to this file -export const { useGetMarketTokenInfoQuery, useGetSecurityTokenInfoQuery } = coingeckoApi +export const { useGetMarketTokenInfoQuery, useGetLiquidityMarketsQuery, useGetSecurityTokenInfoQuery } = coingeckoApi export default coingeckoApi diff --git a/src/state/index.ts b/src/state/index.ts index 863550aca7..9f0ee714a2 100644 --- a/src/state/index.ts +++ b/src/state/index.ts @@ -21,6 +21,7 @@ import tokenApi from 'services/token' import { ENV_LEVEL } from 'constants/env' import { ENV_TYPE } from 'constants/type' +import coinmarketcapApi from 'pages/TrueSightV2/hooks/useCoinmarketcapData' import kyberAIApi from 'pages/TrueSightV2/hooks/useKyberAIData' import application from './application/reducer' @@ -101,6 +102,7 @@ const store = configureStore({ [campaignApi.reducerPath]: campaignApi.reducer, [kyberAIApi.reducerPath]: kyberAIApi.reducer, + [coinmarketcapApi.reducerPath]: coinmarketcapApi.reducer, [kyberAISubscriptionApi.reducerPath]: kyberAISubscriptionApi.reducer, [kyberDAO.reducerPath]: kyberDAO.reducer, [identifyApi.reducerPath]: identifyApi.reducer, @@ -132,6 +134,7 @@ const store = configureStore({ .concat(coingeckoApi.middleware) .concat(limitOrderApi.middleware) .concat(kyberAIApi.middleware) + .concat(coinmarketcapApi.middleware) .concat(campaignApi.middleware) .concat(kyberAISubscriptionApi.middleware) .concat(announcementApi.middleware) diff --git a/src/state/user/reducer.ts b/src/state/user/reducer.ts index ca45336a8e..c24e88dda0 100644 --- a/src/state/user/reducer.ts +++ b/src/state/user/reducer.ts @@ -172,6 +172,8 @@ const initialState: UserState = { liveDEXTrades: true, fundingRateOnCEX: true, liquidationsOnCEX: true, + liquidityProfile: true, + markets: true, }, favoriteTokensByChainId: {}, favoriteTokensByChainIdv2: {},