diff --git a/src/components/swapv2/LimitOrder/ListLimitOrder/RefreshLoading.tsx b/src/components/swapv2/LimitOrder/ListLimitOrder/RefreshLoading.tsx index d3c1d93c1d..fbd471835c 100644 --- a/src/components/swapv2/LimitOrder/ListLimitOrder/RefreshLoading.tsx +++ b/src/components/swapv2/LimitOrder/ListLimitOrder/RefreshLoading.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react' import styled, { css, keyframes } from 'styled-components' import useDebounce from 'hooks/useDebounce' +import useShowLoadingAtLeastTime from 'hooks/useShowLoadingAtLeastTime' import useTheme from 'hooks/useTheme' const INTERVAL_REFETCH_TIME = 10 // seconds @@ -104,11 +105,12 @@ export default function RefreshLoading({ const [countdown, setCountdown] = useState(0) const debouncedRefetchLoading = useDebounce(refetchLoading, 100) + const showLoadingAtLeastTime = useShowLoadingAtLeastTime(debouncedRefetchLoading, 200) useEffect(() => { - if (!refetchLoading && !debouncedRefetchLoading) setCountdown(INTERVAL_REFETCH_TIME * 1_000) - else if (refetchLoading && debouncedRefetchLoading) setCountdown(0) - }, [refetchLoading, debouncedRefetchLoading]) + if (!refetchLoading && !showLoadingAtLeastTime) setCountdown(INTERVAL_REFETCH_TIME * 1_000) + else if (refetchLoading && showLoadingAtLeastTime) setCountdown(0) + }, [refetchLoading, showLoadingAtLeastTime]) useEffect(() => { if (countdown > 0) { diff --git a/src/components/swapv2/LimitOrder/OrderBook/OrderItem.tsx b/src/components/swapv2/LimitOrder/OrderBook/OrderItem.tsx index 20e5128607..c43c6a083a 100644 --- a/src/components/swapv2/LimitOrder/OrderBook/OrderItem.tsx +++ b/src/components/swapv2/LimitOrder/OrderBook/OrderItem.tsx @@ -1,9 +1,11 @@ import { Currency } from '@kyberswap/ks-sdk-core' +import { useMemo } from 'react' import { useMedia } from 'react-use' import { Flex, Text } from 'rebass' import styled, { CSSProperties } from 'styled-components' import CurrencyLogo from 'components/CurrencyLogo' +import useChainsConfig from 'hooks/useChainsConfig' import useTheme from 'hooks/useTheme' import { useLimitState } from 'state/limit/hooks' import { MEDIA_WIDTHS } from 'theme' @@ -14,14 +16,22 @@ export const ItemWrapper = styled.div` font-size: 14px; line-height: 20px; display: grid; - grid-template-columns: 2fr 2fr 2fr 1fr; + grid-template-columns: 1fr 2fr 2fr 2fr 1fr; padding: 12px; ${({ theme }) => theme.mediaWidth.upToSmall` - grid-template-columns: 1.6fr 2fr 2fr 1fr; + grid-template-columns: 1.2fr 1.8fr 2fr 1fr; `} ` +export const ChainImage = styled.img` + height: 16px; + width: 16px; + position: relative; + top: 2px; + left: 10px; +` + const Rate = styled.div<{ reverse?: boolean }>` color: ${({ theme, reverse }) => (reverse ? theme.primary : theme.red)}; ` @@ -48,20 +58,50 @@ export default function OrderItem({ reverse, order, style, + showAmountOut, }: { reverse?: boolean order: LimitOrderFromTokenPairFormatted style: CSSProperties + showAmountOut: boolean }) { const theme = useTheme() const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) - const { currencyIn, currencyOut } = useLimitState() + const { currencyIn: makerCurrency, currencyOut: takerCurrency } = useLimitState() + const { supportedChains } = useChainsConfig() + + const chain = useMemo( + () => supportedChains.find(chain => chain.chainId === order.chainId), + [order.chainId, supportedChains], + ) return ( + {order.rate} - - + {!upToSmall ? ( + <> + + + + ) : ( + + )} {!upToSmall && 'Filled '} {order.filled}% diff --git a/src/components/swapv2/LimitOrder/OrderBook/TableHeader.tsx b/src/components/swapv2/LimitOrder/OrderBook/TableHeader.tsx index 61eb2bc7c9..f8595613a4 100644 --- a/src/components/swapv2/LimitOrder/OrderBook/TableHeader.tsx +++ b/src/components/swapv2/LimitOrder/OrderBook/TableHeader.tsx @@ -1,9 +1,11 @@ import { Trans } from '@lingui/macro' import { rgba } from 'polished' +import { useState } from 'react' import { useMedia } from 'react-use' -import { Text } from 'rebass' +import { Flex, Text } from 'rebass' import styled from 'styled-components' +import { ReactComponent as DropdownSvg } from 'assets/svg/down.svg' import { useLimitState } from 'state/limit/hooks' import { MEDIA_WIDTHS } from 'theme' @@ -17,32 +19,107 @@ const Header = styled(ItemWrapper)` font-weight: 500; padding: 16px 12px; cursor: default; - :hover { + /* :hover { background-color: ${({ theme }) => rgba(theme.primary, 0.2)}; - } + } */ ` -export default function TableHeader() { +const DropdownIcon = styled(DropdownSvg)<{ open: boolean }>` + color: ${({ theme }) => theme.subText}; + transform: rotate(${({ open }) => (open ? '180deg' : '0')}); + transition: transform 300ms; + min-width: 24px; +` + +const TabWrapper = styled.div` + overflow: hidden; + transition: 0.3s ease-in-out; + position: relative; + left: -14px; +` + +const TabContainer = styled.div` + display: flex; + background: ${({ theme }) => theme.buttonBlack}; + border: ${({ theme }) => `1px solid ${theme.border}`}; + width: fit-content; + border-radius: 999px; + padding: 1px; + margin-top: 12px; +` + +const TabItem = styled(Flex)<{ active?: boolean }>` + padding: 4px 8px; + align-items: center; + border-radius: 999px; + background: ${({ theme, active }) => (active ? theme.tabActive : 'transparent')}; + color: ${({ theme, active }) => (active ? theme.text : theme.subText)}; + transition: 0.2s ease-in-out; +` + +export default function TableHeader({ + showAmountOut, + setShowAmountOut, +}: { + showAmountOut: boolean + setShowAmountOut: (value: boolean) => void +}) { const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) const { currencyIn, currencyOut } = useLimitState() + const [openDropdown, setOpenDropdown] = useState(false) + + const onClickDropdown = () => setOpenDropdown(!openDropdown) + const onChangeDisplayedAmount = () => setShowAmountOut(!showAmountOut) return (
+ CHAIN RATE - {upToSmall ?
: ' '}({currencyIn?.symbol || ''}/ - {currencyOut?.symbol || ''}) -
- - AMOUNT - {upToSmall ?
: ' '} - ({currencyIn?.symbol}) -
- - AMOUNT - {upToSmall ?
: ' '} - ({currencyOut?.symbol}) + {!!currencyIn && !!currencyOut && ( + <> + {upToSmall ?
: ' '}({currencyIn?.symbol}/ + {currencyOut?.symbol}) + + )}
+ {!upToSmall && ( + + AMOUNT + {!!currencyIn && ( + <> + {upToSmall ?
: ' '} + ({currencyIn?.symbol}) + + )} +
+ )} +
+ + + AMOUNT + {!!currencyIn && !!currencyOut && ( + <> + {upToSmall ?
: ' '} + ({!upToSmall || showAmountOut ? currencyOut?.symbol : currencyIn?.symbol}) + + )} +
+ {upToSmall && } +
+ {upToSmall && ( + + + + {currencyIn?.symbol} + + + {currencyOut?.symbol} + {' '} + + + )} +
ORDER STATUS diff --git a/src/components/swapv2/LimitOrder/OrderBook/index.tsx b/src/components/swapv2/LimitOrder/OrderBook/index.tsx index 6a8fb2aedc..0c2ae726e2 100644 --- a/src/components/swapv2/LimitOrder/OrderBook/index.tsx +++ b/src/components/swapv2/LimitOrder/OrderBook/index.tsx @@ -1,7 +1,7 @@ -import { Currency, CurrencyAmount } from '@kyberswap/ks-sdk-core' +import { Currency, CurrencyAmount, Token } from '@kyberswap/ks-sdk-core' import { Trans } from '@lingui/macro' import { rgba } from 'polished' -import { useCallback, useEffect, useMemo, useRef } from 'react' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useMedia } from 'react-use' import { FixedSizeList } from 'react-window' import { Text } from 'rebass' @@ -22,7 +22,7 @@ import RefreshLoading from '../ListLimitOrder/RefreshLoading' import { NoResultWrapper } from '../ListOrder' import { groupToMap } from '../helpers' import { LimitOrderFromTokenPair, LimitOrderFromTokenPairFormatted } from '../type' -import OrderItem from './OrderItem' +import OrderItem, { ChainImage } from './OrderItem' import TableHeader from './TableHeader' const ITEMS_DISPLAY = 10 @@ -56,6 +56,12 @@ const MarketPrice = styled.div` font-size: 20px; line-height: 24px; background: ${({ theme }) => rgba(theme.white, 0.04)}; + display: grid; + grid-template-columns: 1fr 2fr 2fr 2fr 1fr; + + ${({ theme }) => theme.mediaWidth.upToSmall` + grid-template-columns: 1.2fr 1.8fr 2fr 1fr; + `} ` const OrderItemWrapper = styled(FixedSizeList)` @@ -89,41 +95,52 @@ const NoDataPanel = () => ( const formatOrders = ( orders: LimitOrderFromTokenPair[], - reverse: boolean, - currencyIn: Currency | undefined, - currencyOut: Currency | undefined, + makerCurrency: Currency | undefined, + takerCurrency: Currency | undefined, significantDigits: number, + reverse = false, ): LimitOrderFromTokenPairFormatted[] => { - if (!currencyIn || !currencyOut) return [] + if (!makerCurrency || !takerCurrency) return [] // Format orders, remove orders that are above 99% filled and sort descending by rate const ordersFormatted = orders .map(order => { - const currencyInAmount = CurrencyAmount.fromRawAmount(!reverse ? currencyIn : currencyOut, order.makingAmount) - const currencyOutAmount = CurrencyAmount.fromRawAmount(!reverse ? currencyOut : currencyIn, order.takingAmount) - const rate = !reverse - ? parseFloat(currencyOutAmount.toExact()) / parseFloat(currencyInAmount.toExact()) - : parseFloat(currencyInAmount.toExact()) / parseFloat(currencyOutAmount.toExact()) - - const firstAmount = (!reverse ? currencyInAmount : currencyOutAmount).toExact() - const secondAmount = (!reverse ? currencyOutAmount : currencyInAmount).toExact() - - const filledMakingAmount = CurrencyAmount.fromRawAmount( - !reverse ? currencyIn : currencyOut, - order.filledMakingAmount, + const newMakerCurrency = new Token( + makerCurrency.chainId, + makerCurrency.wrapped.address, + order.makerAssetDecimals, + makerCurrency.symbol, ) - const filled = (parseFloat(filledMakingAmount.toExact()) / parseFloat(currencyInAmount.toExact())) * 100 + const newTakerCurrency = new Token( + takerCurrency.chainId, + takerCurrency.wrapped.address, + order.takerAssetDecimals, + takerCurrency.symbol, + ) + + const makerCurrencyAmount = CurrencyAmount.fromRawAmount(newMakerCurrency, order.makingAmount) + const takerCurrencyAmount = CurrencyAmount.fromRawAmount(newTakerCurrency, order.takingAmount) + + const rate = ( + !reverse + ? takerCurrencyAmount.divide(makerCurrencyAmount).multiply(makerCurrencyAmount.decimalScale) + : makerCurrencyAmount.divide(takerCurrencyAmount).multiply(takerCurrencyAmount.decimalScale) + ).toSignificant(100) + + const filledMakingAmount = CurrencyAmount.fromRawAmount(newMakerCurrency, order.filledMakingAmount) + const filled = (parseFloat(filledMakingAmount.toExact()) / parseFloat(makerCurrencyAmount.toExact())) * 100 return { id: order.id, + chainId: order.chainId, rate, - firstAmount, - secondAmount, + makerAmount: makerCurrencyAmount.toExact(), + takerAmount: takerCurrencyAmount.toExact(), filled: filled > 99 ? '100' : filled.toFixed(), } }) .filter(order => order.filled !== '100') - .sort((a, b) => b.rate - a.rate) + .sort((a, b) => parseFloat(b.rate) - parseFloat(a.rate)) .map(order => ({ ...order, rate: formatDisplayNumber(order.rate, { significantDigits }), @@ -139,17 +156,15 @@ const formatOrders = ( accumulatorOrder ? { ...currentOrder, - firstAmount: (parseFloat(currentOrder.firstAmount) + parseFloat(accumulatorOrder.firstAmount)).toString(), - secondAmount: ( - parseFloat(currentOrder.secondAmount) + parseFloat(accumulatorOrder.secondAmount) - ).toString(), + makerAmount: (parseFloat(currentOrder.makerAmount) + parseFloat(accumulatorOrder.makerAmount)).toString(), + takerAmount: (parseFloat(currentOrder.takerAmount) + parseFloat(accumulatorOrder.takerAmount)).toString(), } : currentOrder, null, ) if (mergedOrder) { - mergedOrder.firstAmount = formatDisplayNumber(mergedOrder.firstAmount, { significantDigits }) - mergedOrder.secondAmount = formatDisplayNumber(mergedOrder.secondAmount, { significantDigits }) + mergedOrder.makerAmount = formatDisplayNumber(mergedOrder.makerAmount, { significantDigits }) + mergedOrder.takerAmount = formatDisplayNumber(mergedOrder.takerAmount, { significantDigits }) mergedOrders.push(mergedOrder) } }) @@ -161,13 +176,15 @@ export default function OrderBook() { const theme = useTheme() const upToSmall = useMedia(`(max-width: ${MEDIA_WIDTHS.upToSmall}px)`) - const { chainId } = useActiveWeb3React() - const { currencyIn, currencyOut } = useLimitState() + const { chainId, networkInfo } = useActiveWeb3React() + const { currencyIn: makerCurrency, currencyOut: takerCurrency } = useLimitState() const { loading: loadingMarketRate, tradeInfo: { marketRate = 0 } = {}, refetch: refetchMarketRate, - } = useBaseTradeInfoLimitOrder(currencyIn, currencyOut, chainId) + } = useBaseTradeInfoLimitOrder(makerCurrency, takerCurrency, chainId) + + const [showAmountOut, setShowAmountOut] = useState(true) const ordersWrapperRef = useRef>(null) @@ -178,8 +195,8 @@ export default function OrderBook() { isFetching: isFetchingOrders, } = useGetOrdersByTokenPairQuery({ chainId, - makerAsset: currencyIn?.wrapped?.address, - takerAsset: currencyOut?.wrapped?.address, + makerAsset: makerCurrency?.wrapped?.address, + takerAsset: takerCurrency?.wrapped?.address, }) const { data: { orders: reversedOrders = [] } = {}, @@ -188,8 +205,8 @@ export default function OrderBook() { isFetching: isFetchingReversedOrder, } = useGetOrdersByTokenPairQuery({ chainId, - makerAsset: currencyOut?.wrapped?.address, - takerAsset: currencyIn?.wrapped?.address, + makerAsset: takerCurrency?.wrapped?.address, + takerAsset: makerCurrency?.wrapped?.address, }) const loadingOrders = useShowLoadingAtLeastTime(isLoadingOrders) @@ -199,25 +216,26 @@ export default function OrderBook() { () => formatOrders( orders, - false, - currencyIn, - currencyOut, + makerCurrency, + takerCurrency, upToSmall ? MOBILE_SIGNIFICANT_DIGITS : DESKTOP_SIGNIFICANT_DIGITS, ), - [orders, currencyIn, currencyOut, upToSmall], + [orders, makerCurrency, takerCurrency, upToSmall], ) const formattedReversedOrders = useMemo( () => formatOrders( reversedOrders, - true, - currencyIn, - currencyOut, + takerCurrency, + makerCurrency, upToSmall ? MOBILE_SIGNIFICANT_DIGITS : DESKTOP_SIGNIFICANT_DIGITS, + true, ), - [reversedOrders, currencyIn, currencyOut, upToSmall], + [reversedOrders, takerCurrency, makerCurrency, upToSmall], ) + const refetchActive = useMemo(() => !!makerCurrency && !!takerCurrency, [makerCurrency, takerCurrency]) + const refetchLoading = useMemo( () => loadingMarketRate || isFetchingOrders || isFetchingReversedOrder, [loadingMarketRate, isFetchingOrders, isFetchingReversedOrder], @@ -242,14 +260,16 @@ export default function OrderBook() { ) : ( <> - - - Orders refresh in - {' '} - - + {refetchActive && ( + + + Orders refresh in + {' '} + + + )} - + {formattedOrders.length > 0 ? ( {({ index, style }: { index: number; style: CSSProperties }) => { const order = formattedOrders[index] - return + return }} ) : ( )} - {marketRate && ( + {!!marketRate && ( + {formatDisplayNumber(marketRate, { significantDigits: upToSmall ? MOBILE_SIGNIFICANT_DIGITS : DESKTOP_SIGNIFICANT_DIGITS, })} @@ -288,7 +309,7 @@ export default function OrderBook() { > {({ index, style }: { index: number; style: CSSProperties }) => { const order = formattedReversedOrders[index] - return + return }} ) : ( diff --git a/src/components/swapv2/LimitOrder/type.ts b/src/components/swapv2/LimitOrder/type.ts index c7a2e1a026..cd8a30bbce 100644 --- a/src/components/swapv2/LimitOrder/type.ts +++ b/src/components/swapv2/LimitOrder/type.ts @@ -82,13 +82,16 @@ export type LimitOrderFromTokenPair = { orderHash: string availableMakingAmount: string makerBalanceAllowance: string + makerAssetDecimals: number + takerAssetDecimals: number } export type LimitOrderFromTokenPairFormatted = { id: number + chainId: ChainId rate: string - firstAmount: string - secondAmount: string + makerAmount: string + takerAmount: string filled: string } diff --git a/src/pages/SwapV3/index.tsx b/src/pages/SwapV3/index.tsx index 38b69aad4e..67f13e3d3a 100644 --- a/src/pages/SwapV3/index.tsx +++ b/src/pages/SwapV3/index.tsx @@ -106,8 +106,11 @@ export default function Swap() { const outputCurrency = searchParams.get('outputCurrency') if (inputCurrency || outputCurrency) { - navigate(`/swap/${NETWORKS_INFO[chainId].route}/${inputCurrency || ''}-to-${outputCurrency || ''}`) + if (pathname.includes(APP_PATHS.LIMIT)) + navigate(`${APP_PATHS.LIMIT}/${NETWORKS_INFO[chainId].route}/${inputCurrency || ''}-to-${outputCurrency || ''}`) + else navigate(`/swap/${NETWORKS_INFO[chainId].route}/${inputCurrency || ''}-to-${outputCurrency || ''}`) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchParams, chainId, navigate]) const shouldHighlightSwapBox = qs.highlightBox === 'true' diff --git a/src/services/limitOrder.ts b/src/services/limitOrder.ts index 15fc1eb020..ecc4b44c04 100644 --- a/src/services/limitOrder.ts +++ b/src/services/limitOrder.ts @@ -70,7 +70,7 @@ const limitOrderApi = createApi({ } >({ query: params => ({ - url: `${LIMIT_ORDER_API_READ_PARTNER}/v1/orders`, + url: `${LIMIT_ORDER_API_READ_PARTNER}/v1/orders/allchains`, params, }), transformResponse: ({ data }: any) => { @@ -79,6 +79,13 @@ const limitOrderApi = createApi({ }) return { orders: data?.orders || [] } }, + async onQueryStarted(agr, { dispatch, queryFulfilled }) { + try { + await queryFulfilled + } catch { + dispatch(limitOrderApi.util.upsertQueryData('getOrdersByTokenPair', agr, { orders: [] })) + } + }, providesTags: [RTK_QUERY_TAGS.GET_ORDERS_BY_TOKEN_PAIR], }), getNumberOfInsufficientFundOrders: builder.query({