From 175f4e8dfe2a75ffb2e37a546ab0096bddaf235e Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 5 Apr 2023 19:51:55 +0600 Subject: [PATCH 01/10] Execution time for trades table --- src/api/operator/types.ts | 2 +- .../orders/OrderDetails/FillsTable.tsx | 12 +- src/hooks/useOperatorTrades.ts | 109 ++++++++---------- src/utils/operator.ts | 7 +- 4 files changed, 63 insertions(+), 67 deletions(-) diff --git a/src/api/operator/types.ts b/src/api/operator/types.ts index 18279644..0878cff3 100644 --- a/src/api/operator/types.ts +++ b/src/api/operator/types.ts @@ -66,7 +66,7 @@ export type Trade = Pick thead { @@ -63,7 +64,7 @@ const Wrapper = styled(StyledUserDetailsTable)` } > thead > tr, > tbody > tr { - grid-template-columns: 4fr 2fr 3fr 3fr 3fr 4fr 4fr; + grid-template-columns: 4fr 2fr 3fr 3fr 3.5fr 3fr 4fr; } > tbody > tr > td:nth-child(8), > thead > tr > th:nth-child(8) { @@ -182,6 +183,11 @@ const StyledLinkButton = styled(LinkButton)` } ` +const StyledShimmerBar = styled(ShimmerBar)` + min-height: 20px; + min-width: 100px; +` + export type Props = StyledUserDetailsTableProps & { trades: Trade[] | undefined order: Order | null @@ -224,8 +230,6 @@ const RowFill: React.FC = ({ trade, isPriceInversed }) => { const buyToken = tokens[buyTokenAddress] const sellToken = tokens[sellTokenAddress] - const executionTimeFormatted = - executionTime instanceof Date && !isNaN(Date.parse(executionTime.toString())) ? executionTime : new Date() const executionPrice = calculateExecutionPrice(isPriceInversed, sellAmount, buyAmount, sellToken, buyToken) const executionToken = isPriceInversed ? buyToken : sellToken @@ -272,7 +276,7 @@ const RowFill: React.FC = ({ trade, isPriceInversed }) => { Execution time - {} + {executionTime ? : } diff --git a/src/hooks/useOperatorTrades.ts b/src/hooks/useOperatorTrades.ts index 2d7e03f3..bc076ff9 100644 --- a/src/hooks/useOperatorTrades.ts +++ b/src/hooks/useOperatorTrades.ts @@ -1,14 +1,9 @@ -import { Trade as TradeMetaData } from '@cowprotocol/cow-sdk' -import { getTrades, Order, Trade } from 'api/operator' +import { getTrades, Order, RawTrade, Trade } from 'api/operator' import { useCallback, useEffect, useState } from 'react' import { useNetworkId } from 'state/network' import { Network, UiError } from 'types' import { transformTrade } from 'utils' - -type Params = { - owner?: string - orderId?: string -} +import { web3 } from 'apps/explorer/api' type Result = { trades: Trade[] @@ -16,53 +11,20 @@ type Result = { isLoading: boolean } -/** - * Fetches trades for given filters - * When no filter is given, fetches all trades for current network - */ -export function useTrades(params: Params): Result { - const { owner, orderId } = params - - const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState() - const [trades, setTrades] = useState([]) - - // Here we assume that we are already in the right network - // contrary to useOrder hook, where it searches all networks for a given orderId - const networkId = useNetworkId() +async function fetchTradesTimestamps(rawTrades: RawTrade[]): Promise<{[txHash: string]: number}> { + const requests = rawTrades.map(({ txHash, blockNumber }) => { + return web3.eth.getBlock(blockNumber).then(res => ({ + txHash, + timestamp: +res.timestamp + })) + }) - const fetchTrades = useCallback(async (networkId: Network, owner?: string, orderId?: string): Promise => { - setIsLoading(true) - - try { - let trades: TradeMetaData[] = [] - if (orderId) { - trades = await getTrades({ networkId, orderId }) - } else if (owner) { - trades = await getTrades({ networkId, owner }) - } + const data = await Promise.all(requests) - // TODO: fetch buy/sellToken objects - setTrades(trades.map((trade) => transformTrade(trade))) - setError(undefined) - } catch (e) { - const msg = `Failed to fetch trades` - console.error(msg, e) - setError({ message: msg, type: 'error' }) - } finally { - setIsLoading(false) - } - }, []) - - useEffect(() => { - if (!networkId) { - return - } - - fetchTrades(networkId, owner, orderId) - }, [fetchTrades, networkId, orderId, owner]) - - return { trades, error, isLoading } + return data.reduce((acc, val) => { + acc[val.txHash] = val.timestamp + return acc + }, {}) } /** @@ -72,44 +34,75 @@ export function useOrderTrades(order: Order | null): Result { const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState() const [trades, setTrades] = useState([]) + const [rawTrades, setRawTrades] = useState([]) + const [tradesTimestamps, setTradesTimestamps] = useState<{[txHash: string]: number}>({}) // Here we assume that we are already in the right network // contrary to useOrder hook, where it searches all networks for a given orderId const networkId = useNetworkId() const fetchTrades = useCallback( - async (controller: AbortController, _networkId: Network, _order: Order): Promise => { + async (controller: AbortController, _networkId: Network): Promise => { + if (!order) return + setIsLoading(true) - const { uid: orderId, buyToken, sellToken } = _order + const { uid: orderId } = order try { - const _trades = await getTrades({ networkId: _networkId, orderId }) + const trades = await getTrades({ networkId: _networkId, orderId }) if (controller.signal.aborted) return - setTrades(_trades.map((trade) => ({ ...transformTrade(trade), buyToken, sellToken }))) + setRawTrades(trades) setError(undefined) } catch (e) { const msg = `Failed to fetch trades` console.error(msg, e) + setError({ message: msg, type: 'error' }) } finally { setIsLoading(false) } }, - [], + [order], ) + // Fetch blocks timestamps for trades + useEffect(() => { + setTradesTimestamps({}) + + fetchTradesTimestamps(rawTrades).then(setTradesTimestamps).catch(error => { + console.error('Trades timestamps fetching error: ', error) + + setTradesTimestamps({}) + }) + }, [rawTrades]) + + // Transform trades adding tokens and timestamps + useEffect(() => { + if (!order) return + + const { buyToken, sellToken } = order + + const trades = rawTrades.map((trade) => { + return { ...transformTrade(trade, tradesTimestamps[trade.txHash]), buyToken, sellToken } + }) + + setTrades(trades) + }, [order, rawTrades, tradesTimestamps]) + const executedSellAmount = order?.executedSellAmount.toString() const executedBuyAmount = order?.executedBuyAmount.toString() + useEffect(() => { if (!networkId || !order?.uid) { return } + const controller = new AbortController() - fetchTrades(controller, networkId, order) + fetchTrades(controller, networkId) return (): void => controller.abort() // Depending on order UID to avoid re-fetching when obj changes but ID remains the same // Depending on `executedBuy/SellAmount`s string to force a refetch when there are new trades diff --git a/src/utils/operator.ts b/src/utils/operator.ts index db43f5fc..dbcdcbea 100644 --- a/src/utils/operator.ts +++ b/src/utils/operator.ts @@ -351,13 +351,13 @@ export function transformOrder(rawOrder: RawOrder): Order { filledPercentage, surplusAmount, surplusPercentage, - } + } as Order } /** * Transforms a RawTrade into a Trade object */ -export function transformTrade(rawTrade: TradeMetaData & { executionTime?: string }): Trade { +export function transformTrade(rawTrade: TradeMetaData, executionTimestamp?: number): Trade { const { orderUid, buyAmount, @@ -365,7 +365,6 @@ export function transformTrade(rawTrade: TradeMetaData & { executionTime?: strin sellAmountBeforeFees, buyToken, sellToken, - executionTime = '', ...rest } = rawTrade @@ -377,6 +376,6 @@ export function transformTrade(rawTrade: TradeMetaData & { executionTime?: strin sellAmountBeforeFees: new BigNumber(sellAmountBeforeFees), buyTokenAddress: buyToken, sellTokenAddress: sellToken, - executionTime: new Date(executionTime) || null, + executionTime: executionTimestamp ? new Date(executionTimestamp * 1000) : null, } } From 172502a0647528d535a61ea88f4cae0729102679 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 5 Apr 2023 20:01:43 +0600 Subject: [PATCH 02/10] TradesTimestamps type --- src/hooks/useOperatorTrades.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/hooks/useOperatorTrades.ts b/src/hooks/useOperatorTrades.ts index bc076ff9..60f3cb87 100644 --- a/src/hooks/useOperatorTrades.ts +++ b/src/hooks/useOperatorTrades.ts @@ -11,7 +11,9 @@ type Result = { isLoading: boolean } -async function fetchTradesTimestamps(rawTrades: RawTrade[]): Promise<{[txHash: string]: number}> { +type TradesTimestamps = { [txHash: string]: number } + +async function fetchTradesTimestamps(rawTrades: RawTrade[]): Promise { const requests = rawTrades.map(({ txHash, blockNumber }) => { return web3.eth.getBlock(blockNumber).then(res => ({ txHash, @@ -24,7 +26,7 @@ async function fetchTradesTimestamps(rawTrades: RawTrade[]): Promise<{[txHash: s return data.reduce((acc, val) => { acc[val.txHash] = val.timestamp return acc - }, {}) + }, {} as TradesTimestamps) } /** @@ -35,7 +37,7 @@ export function useOrderTrades(order: Order | null): Result { const [error, setError] = useState() const [trades, setTrades] = useState([]) const [rawTrades, setRawTrades] = useState([]) - const [tradesTimestamps, setTradesTimestamps] = useState<{[txHash: string]: number}>({}) + const [tradesTimestamps, setTradesTimestamps] = useState({}) // Here we assume that we are already in the right network // contrary to useOrder hook, where it searches all networks for a given orderId From 569794b3360644999d4b26ab994feaf58f070a37 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 5 Apr 2023 20:05:03 +0600 Subject: [PATCH 03/10] Fix type --- src/hooks/useOperatorTrades.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hooks/useOperatorTrades.ts b/src/hooks/useOperatorTrades.ts index 60f3cb87..53b2be54 100644 --- a/src/hooks/useOperatorTrades.ts +++ b/src/hooks/useOperatorTrades.ts @@ -24,7 +24,8 @@ async function fetchTradesTimestamps(rawTrades: RawTrade[]): Promise { - acc[val.txHash] = val.timestamp + if (val.txHash) acc[val.txHash] = val.timestamp + return acc }, {} as TradesTimestamps) } From dacbcd1ca3f7d322d86c08a26c7c5e65368826a0 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Wed, 5 Apr 2023 20:08:09 +0600 Subject: [PATCH 04/10] Fix type --- src/hooks/useOperatorTrades.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hooks/useOperatorTrades.ts b/src/hooks/useOperatorTrades.ts index 53b2be54..6ac09ffa 100644 --- a/src/hooks/useOperatorTrades.ts +++ b/src/hooks/useOperatorTrades.ts @@ -89,7 +89,9 @@ export function useOrderTrades(order: Order | null): Result { const { buyToken, sellToken } = order const trades = rawTrades.map((trade) => { - return { ...transformTrade(trade, tradesTimestamps[trade.txHash]), buyToken, sellToken } + const timestamp = trade.txHash ? tradesTimestamps[trade.txHash] : undefined + + return { ...transformTrade(trade, timestamp), buyToken, sellToken } }) setTrades(trades) From 7f8b0d617b5baf1991b6e2bef406789ba3b627e8 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 6 Apr 2023 13:07:02 +0600 Subject: [PATCH 05/10] Cache for fetchTradesTimestamps() --- src/hooks/useOperatorTrades.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/hooks/useOperatorTrades.ts b/src/hooks/useOperatorTrades.ts index 6ac09ffa..f58aac9e 100644 --- a/src/hooks/useOperatorTrades.ts +++ b/src/hooks/useOperatorTrades.ts @@ -13,12 +13,23 @@ type Result = { type TradesTimestamps = { [txHash: string]: number } +const tradesTimestampsCache: { [blockNumber: number]: number } = {} + async function fetchTradesTimestamps(rawTrades: RawTrade[]): Promise { const requests = rawTrades.map(({ txHash, blockNumber }) => { - return web3.eth.getBlock(blockNumber).then(res => ({ - txHash, - timestamp: +res.timestamp - })) + const cachedValue = tradesTimestampsCache[blockNumber] + + if (cachedValue) { + return { txHash, timestamp: cachedValue } + } + + return web3.eth.getBlock(blockNumber).then(res => { + const timestamp = +res.timestamp + + tradesTimestampsCache[blockNumber] = timestamp + + return { txHash, timestamp } + }) }) const data = await Promise.all(requests) From 068988c34c5ac714f300a8eb51d19c15d5fa921a Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 6 Apr 2023 16:58:16 +0600 Subject: [PATCH 06/10] Fixed blinking of fills table isFirstLoading - seems to be excessive, because we should display loader only once at start and areTokensLoaded is enough --- .../OrderDetails/FillsTableWithData.tsx | 27 +++---------------- src/hooks/useOperatorTrades.ts | 2 -- 2 files changed, 3 insertions(+), 26 deletions(-) diff --git a/src/components/orders/OrderDetails/FillsTableWithData.tsx b/src/components/orders/OrderDetails/FillsTableWithData.tsx index 60f288d9..394b2956 100644 --- a/src/components/orders/OrderDetails/FillsTableWithData.tsx +++ b/src/components/orders/OrderDetails/FillsTableWithData.tsx @@ -1,6 +1,5 @@ -import React, { useContext, useState, useEffect } from 'react' +import React, { useContext } from 'react' -import { DEFAULT_TIMEOUT } from 'const' import { Order } from 'api/operator' import { EmptyItemWrapper } from 'components/common/StyledUserDetailsTable' import { FillsTableContext } from './context/FillsTableContext' @@ -13,30 +12,10 @@ export const FillsTableWithData: React.FC<{ areTokensLoaded: boolean; order: Ord areTokensLoaded, order, }) => { - const { trades, isLoading, tableState } = useContext(FillsTableContext) + const { trades, tableState } = useContext(FillsTableContext) const isFirstRender = useFirstRender() - const [isFirstLoading, setIsFirstLoading] = useState(true) - useEffect(() => { - setIsFirstLoading(true) - }, [isLoading]) - - useEffect(() => { - let timeOutMs = 0 - if (!trades) { - timeOutMs = DEFAULT_TIMEOUT - } - - const timeOutId: NodeJS.Timeout = setTimeout(() => { - setIsFirstLoading(false) - }, timeOutMs) - - return (): void => { - clearTimeout(timeOutId) - } - }, [trades, trades?.length]) - - return isFirstRender || isFirstLoading || !areTokensLoaded ? ( + return isFirstRender || !areTokensLoaded ? ( diff --git a/src/hooks/useOperatorTrades.ts b/src/hooks/useOperatorTrades.ts index f58aac9e..3c27c4d3 100644 --- a/src/hooks/useOperatorTrades.ts +++ b/src/hooks/useOperatorTrades.ts @@ -84,8 +84,6 @@ export function useOrderTrades(order: Order | null): Result { // Fetch blocks timestamps for trades useEffect(() => { - setTradesTimestamps({}) - fetchTradesTimestamps(rawTrades).then(setTradesTimestamps).catch(error => { console.error('Trades timestamps fetching error: ', error) From 8c912de8fbf569bdbf48326cf485345ef4485029 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 6 Apr 2023 17:19:22 +0600 Subject: [PATCH 07/10] Fixed lint errors and remove node version restriction --- .editorconfig | 15 +++++++++++++++ package.json | 3 --- src/hooks/useOperatorTrades.ts | 24 ++++++++++++------------ src/utils/operator.ts | 10 +--------- 4 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..610fc82f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf + +[{*.ts,*.tsx}] +ij_typescript_force_quote_style = true +ij_typescript_use_double_quotes = false +ij_typescript_use_semicolon_after_statement = false diff --git a/package.json b/package.json index a180742a..6e841209 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,6 @@ "description": "", "main": "src/index.js", "sideEffects": false, - "engines": { - "node": "16" - }, "scripts": { "start": "npm run start:explorer", "start:explorer": "APP=explorer npm run start:apps", diff --git a/src/hooks/useOperatorTrades.ts b/src/hooks/useOperatorTrades.ts index 3c27c4d3..33ba79a6 100644 --- a/src/hooks/useOperatorTrades.ts +++ b/src/hooks/useOperatorTrades.ts @@ -13,23 +13,21 @@ type Result = { type TradesTimestamps = { [txHash: string]: number } -const tradesTimestampsCache: { [blockNumber: number]: number } = {} +const tradesTimestampsCache: { [blockNumber: number]: Promise } = {} async function fetchTradesTimestamps(rawTrades: RawTrade[]): Promise { const requests = rawTrades.map(({ txHash, blockNumber }) => { const cachedValue = tradesTimestampsCache[blockNumber] if (cachedValue) { - return { txHash, timestamp: cachedValue } + return cachedValue.then((timestamp) => ({ txHash, timestamp })) } - return web3.eth.getBlock(blockNumber).then(res => { - const timestamp = +res.timestamp + const request = web3.eth.getBlock(blockNumber).then(({ timestamp }) => +timestamp) - tradesTimestampsCache[blockNumber] = timestamp + tradesTimestampsCache[blockNumber] = request - return { txHash, timestamp } - }) + return request.then((timestamp) => ({ txHash, timestamp })) }) const data = await Promise.all(requests) @@ -64,7 +62,7 @@ export function useOrderTrades(order: Order | null): Result { const { uid: orderId } = order try { - const trades = await getTrades({ networkId: _networkId, orderId }) + const trades = await getTrades({ networkId: _networkId, orderId }) if (controller.signal.aborted) return @@ -84,11 +82,13 @@ export function useOrderTrades(order: Order | null): Result { // Fetch blocks timestamps for trades useEffect(() => { - fetchTradesTimestamps(rawTrades).then(setTradesTimestamps).catch(error => { - console.error('Trades timestamps fetching error: ', error) + fetchTradesTimestamps(rawTrades) + .then(setTradesTimestamps) + .catch((error) => { + console.error('Trades timestamps fetching error: ', error) - setTradesTimestamps({}) - }) + setTradesTimestamps({}) + }) }, [rawTrades]) // Transform trades adding tokens and timestamps diff --git a/src/utils/operator.ts b/src/utils/operator.ts index dbcdcbea..6d74545c 100644 --- a/src/utils/operator.ts +++ b/src/utils/operator.ts @@ -358,15 +358,7 @@ export function transformOrder(rawOrder: RawOrder): Order { * Transforms a RawTrade into a Trade object */ export function transformTrade(rawTrade: TradeMetaData, executionTimestamp?: number): Trade { - const { - orderUid, - buyAmount, - sellAmount, - sellAmountBeforeFees, - buyToken, - sellToken, - ...rest - } = rawTrade + const { orderUid, buyAmount, sellAmount, sellAmountBeforeFees, buyToken, sellToken, ...rest } = rawTrade return { ...rest, From 42b9aeef2f5e3875da0ea5cd7f812d4a7eae7119 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 6 Apr 2023 17:27:46 +0600 Subject: [PATCH 08/10] Rollback node restriction --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 6e841209..a180742a 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "description": "", "main": "src/index.js", "sideEffects": false, + "engines": { + "node": "16" + }, "scripts": { "start": "npm run start:explorer", "start:explorer": "APP=explorer npm run start:apps", From bde686c0871625502c584c19ebbff36de185f6e2 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 6 Apr 2023 17:38:18 +0600 Subject: [PATCH 09/10] Changed isLoading flag meaning in useOrderTrades() Now it actually means isFirstLoading --- src/hooks/useOperatorTrades.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/hooks/useOperatorTrades.ts b/src/hooks/useOperatorTrades.ts index 33ba79a6..f848ea39 100644 --- a/src/hooks/useOperatorTrades.ts +++ b/src/hooks/useOperatorTrades.ts @@ -43,10 +43,9 @@ async function fetchTradesTimestamps(rawTrades: RawTrade[]): Promise() const [trades, setTrades] = useState([]) - const [rawTrades, setRawTrades] = useState([]) + const [rawTrades, setRawTrades] = useState(null) const [tradesTimestamps, setTradesTimestamps] = useState({}) // Here we assume that we are already in the right network @@ -57,8 +56,6 @@ export function useOrderTrades(order: Order | null): Result { async (controller: AbortController, _networkId: Network): Promise => { if (!order) return - setIsLoading(true) - const { uid: orderId } = order try { @@ -72,9 +69,8 @@ export function useOrderTrades(order: Order | null): Result { const msg = `Failed to fetch trades` console.error(msg, e) + setRawTrades([]) setError({ message: msg, type: 'error' }) - } finally { - setIsLoading(false) } }, [order], @@ -82,6 +78,8 @@ export function useOrderTrades(order: Order | null): Result { // Fetch blocks timestamps for trades useEffect(() => { + if (!rawTrades) return + fetchTradesTimestamps(rawTrades) .then(setTradesTimestamps) .catch((error) => { @@ -93,7 +91,7 @@ export function useOrderTrades(order: Order | null): Result { // Transform trades adding tokens and timestamps useEffect(() => { - if (!order) return + if (!order || !rawTrades) return const { buyToken, sellToken } = order @@ -124,5 +122,5 @@ export function useOrderTrades(order: Order | null): Result { // eslint-disable-next-line react-hooks/exhaustive-deps }, [fetchTrades, networkId, order?.uid, executedSellAmount, executedBuyAmount]) - return { trades, error, isLoading } + return { trades, error, isLoading: rawTrades === null } } From 32c8f608d185369ef450a8350fb92d7f42f53af4 Mon Sep 17 00:00:00 2001 From: Alexandr Kazachenko Date: Thu, 6 Apr 2023 17:44:14 +0600 Subject: [PATCH 10/10] Fix lint errors (#426) --- .../orders/FilledProgress/index.tsx | 15 ++++- .../orders/OrderDetails/FillsTable.tsx | 59 ++++++++++++++----- src/components/token/TokenAmount/index.tsx | 22 +++++-- 3 files changed, 73 insertions(+), 23 deletions(-) diff --git a/src/components/orders/FilledProgress/index.tsx b/src/components/orders/FilledProgress/index.tsx index 0218287b..071ff1e1 100644 --- a/src/components/orders/FilledProgress/index.tsx +++ b/src/components/orders/FilledProgress/index.tsx @@ -43,18 +43,22 @@ const TableHeading = styled.div` padding: 1.6rem; display: flex; gap: 2rem; + ${media.mobile} { flex-direction: column; gap: 1rem; } + .title { text-transform: uppercase; font-size: 1.1rem; } + .fillNumber { font-size: 3.2rem; margin: 1.5rem 0 1rem 0; color: ${({ theme }): string => theme.green}; + ${media.mobile} { font-size: 2.8rem; } @@ -63,9 +67,11 @@ const TableHeading = styled.div` .priceNumber { font-size: 2.2rem; margin: 1rem 0; + ${media.mobile} { font-size: 1.8rem; } + span { line-height: 1; } @@ -77,12 +83,15 @@ const TableHeadingContent = styled.div` flex-direction: column; justify-content: center; width: 27rem; + ${media.mobile} { flex-direction: column; } + .progress-line { width: 100%; } + &.limit-price { width: 38rem; } @@ -147,14 +156,14 @@ export function FilledProgress(props: Props): JSX.Element { {/* Executed part (bought/sold tokens) */} - + {' '} {!fullyFilled && ( // Show the total amount to buy/sell. Only for orders that are not 100% executed <> of{' '} - + {' '} )} @@ -166,7 +175,7 @@ export function FilledProgress(props: Props): JSX.Element { <> for a total of{' '} - + )} diff --git a/src/components/orders/OrderDetails/FillsTable.tsx b/src/components/orders/OrderDetails/FillsTable.tsx index 27efa6ad..2f3aad86 100644 --- a/src/components/orders/OrderDetails/FillsTable.tsx +++ b/src/components/orders/OrderDetails/FillsTable.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useCallback, useState } from 'react' import styled from 'styled-components' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faExchangeAlt, faProjectDiagram } from '@fortawesome/free-solid-svg-icons' @@ -32,13 +32,17 @@ const Wrapper = styled(StyledUserDetailsTable)` padding: 0 2rem; } } + > tbody { min-height: 37rem; border-bottom: 0.1rem solid ${({ theme }): string => theme.tableRowBorder}; + > tr { min-height: 7.4rem; + &.header-row { display: none; + ${media.mobile} { display: flex; background: transparent; @@ -47,10 +51,12 @@ const Wrapper = styled(StyledUserDetailsTable)` margin: 0; box-shadow: none; min-height: 2rem; + td { padding: 0; margin: 0; margin-top: 1rem; + .mobile-header { margin: 0; } @@ -58,35 +64,43 @@ const Wrapper = styled(StyledUserDetailsTable)` } } } + > tr > td:first-child { padding: 0 2rem; } } + > thead > tr, > tbody > tr { grid-template-columns: 4fr 2fr 3fr 3fr 3.5fr 3fr 4fr; } + > tbody > tr > td:nth-child(8), > thead > tr > th:nth-child(8) { justify-content: center; } + tr > td { span.span-inside-tooltip { display: flex; flex-direction: row; flex-wrap: wrap; + img { padding: 0; } } } + ${media.mobile} { > thead > tr { display: none; + > th:first-child { padding: 0 1rem; } } + > tbody > tr { grid-template-columns: none; border: 0.1rem solid ${({ theme }): string => theme.tableRowBorder}; @@ -94,14 +108,17 @@ const Wrapper = styled(StyledUserDetailsTable)` border-radius: 6px; margin-top: 10px; padding: 12px; + &:hover { background: none; backdrop-filter: none; } + td:first-child { padding: 0 1rem; } } + tr > td { display: flex; flex: 1; @@ -110,14 +127,17 @@ const Wrapper = styled(StyledUserDetailsTable)` margin: 0; margin-bottom: 18px; min-height: 32px; + span.span-inside-tooltip { align-items: flex-end; flex-direction: column; + img { margin-left: 0; } } } + > tbody > tr > td, > thead > tr > th { :nth-child(4), @@ -128,32 +148,39 @@ const Wrapper = styled(StyledUserDetailsTable)` justify-content: space-between; } } + .header-value { flex-wrap: wrap; text-align: end; } + .span-copybtn-wrap { display: flex; flex-wrap: nowrap; + span { display: flex; align-items: center; } + .copy-text { display: none; } } } + overflow: auto; ` const HeaderTitle = styled.span` display: none; + ${media.mobile} { font-weight: 600; align-items: center; display: flex; margin-right: 3rem; + svg { margin-left: 5px; } @@ -161,6 +188,7 @@ const HeaderTitle = styled.span` ` const HeaderValue = styled.span<{ captionColor?: 'green' | 'red1' | 'grey' }>` color: ${({ theme, captionColor }): string => (captionColor ? theme[captionColor] : theme.textPrimary1)}; + ${media.mobile} { flex-wrap: wrap; text-align: end; @@ -206,7 +234,7 @@ function calculateExecutionPrice( sellAmount: BigNumber, buyAmount: BigNumber, sellToken?: TokenErc20 | null, - buyToken?: TokenErc20 | null + buyToken?: TokenErc20 | null, ): BigNumber | null { if (!sellToken || !buyToken) return null @@ -216,9 +244,7 @@ function calculateExecutionPrice( return calculatePrice({ numerator: isPriceInverted ? buyData : sellData, denominator: isPriceInverted ? sellData : buyData, - }).multipliedBy( - TEN_BIG_NUMBER.exponentiatedBy((isPriceInverted ? buyToken : sellToken).decimals) - ) + }).multipliedBy(TEN_BIG_NUMBER.exponentiatedBy((isPriceInverted ? buyToken : sellToken).decimals)) } const RowFill: React.FC = ({ trade, isPriceInverted, invertButton }) => { @@ -260,24 +286,24 @@ const RowFill: React.FC = ({ trade, isPriceInverted, invertButton }) = Buy amount - + Sell amount - + Execution price {invertButton} - - {executionPrice && } - + {executionPrice && } Execution time - {executionTime ? : } + + {executionTime ? : } + @@ -296,7 +322,11 @@ const FillsTable: React.FC = (props) => { const { trades, order, tableState, showBorderTable = false } = props const [isPriceInverted, setIsPriceInverted] = useState(false) - const invertButton = setIsPriceInverted(value => !value)} /> + const onInvert = useCallback(() => { + setIsPriceInverted((value) => !value) + }, []) + + const invertButton = const tradeItems = (items: Trade[] | undefined): JSX.Element => { if (!items || items.length === 0) { @@ -310,7 +340,7 @@ const FillsTable: React.FC = (props) => { ) } else { - return ( + return ( <> {items.map((item, i) => ( = (props) => { index={i + tableState.pageOffset} trade={item} invertButton={invertButton} - isPriceInverted={isPriceInverted} /> + isPriceInverted={isPriceInverted} + /> ))} ) diff --git a/src/components/token/TokenAmount/index.tsx b/src/components/token/TokenAmount/index.tsx index f3717380..109f824b 100644 --- a/src/components/token/TokenAmount/index.tsx +++ b/src/components/token/TokenAmount/index.tsx @@ -3,11 +3,21 @@ import BigNumber from 'bignumber.js' import { TokenErc20 } from '@gnosis.pm/dex-js' import { FormatAmountPrecision, formatSmartMaxPrecision, formattingAmountPrecision } from 'utils' +interface Props { + amount: BigNumber + symbol?: string + token?: TokenErc20 | null +} + // TODO: unify with TokenAmount in @cowprotocol/cowswap -export function TokenAmount({ amount, token, symbol }: { amount: BigNumber, symbol?: string, token?: TokenErc20 | null }) { - const fullAmount = formatSmartMaxPrecision(amount, token || null) - const displayedAmount = formattingAmountPrecision(amount, token || null, FormatAmountPrecision.highPrecision) - const displayedSymbol = symbol || token?.symbol +export const TokenAmount: React.FC = ({ amount, token, symbol }: Props) => { + const fullAmount = formatSmartMaxPrecision(amount, token || null) + const displayedAmount = formattingAmountPrecision(amount, token || null, FormatAmountPrecision.highPrecision) + const displayedSymbol = symbol || token?.symbol - return {displayedAmount} {displayedSymbol} -} \ No newline at end of file + return ( + + {displayedAmount} {displayedSymbol} + + ) +}