From 5a6d4cd088ba852bda36836365b872b82f6404eb Mon Sep 17 00:00:00 2001 From: Anxo Rodriguez Date: Mon, 16 Dec 2024 20:46:09 +0000 Subject: [PATCH 1/9] feat: create a warning banner for unsigned orders --- .../orders/UnsignedOrderWarning/index.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 apps/explorer/src/components/orders/UnsignedOrderWarning/index.tsx diff --git a/apps/explorer/src/components/orders/UnsignedOrderWarning/index.tsx b/apps/explorer/src/components/orders/UnsignedOrderWarning/index.tsx new file mode 100644 index 0000000000..ef5a8addef --- /dev/null +++ b/apps/explorer/src/components/orders/UnsignedOrderWarning/index.tsx @@ -0,0 +1,14 @@ +import { BannerOrientation, InlineBanner } from '@cowprotocol/ui' +import styled from 'styled-components' + +const StyledInlineBanner = styled(InlineBanner)` + --cow-color-danger-text: ${({ theme }): string => theme.alert2}; +` + +export const UnsignedOrderWarning: React.FC = () => { + return ( + + An unsigned order is not necessarily placed by the owner's account. Please be cautious. + + ) +} From b5abc2cf045cba52e1ab15e14a95ab9ca18cd8e6 Mon Sep 17 00:00:00 2001 From: Anxo Rodriguez Date: Mon, 16 Dec 2024 20:46:55 +0000 Subject: [PATCH 2/9] feat: add a toggle filter --- .../OrdersUserDetailsTable/ToggleFilter.tsx | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 apps/explorer/src/components/orders/OrdersUserDetailsTable/ToggleFilter.tsx diff --git a/apps/explorer/src/components/orders/OrdersUserDetailsTable/ToggleFilter.tsx b/apps/explorer/src/components/orders/OrdersUserDetailsTable/ToggleFilter.tsx new file mode 100644 index 0000000000..03eca693b8 --- /dev/null +++ b/apps/explorer/src/components/orders/OrdersUserDetailsTable/ToggleFilter.tsx @@ -0,0 +1,37 @@ +import React from 'react' +import styled from 'styled-components' + +interface BadgeProps { + checked: boolean + onChange: () => void + label: string + count: number +} + +const Wrapper = styled.div<{ checked: boolean }>` + display: inline-block; + padding: 5px 10px; + border-radius: 20px; + background-color: ${({ checked }) => (checked ? '#007bff' : '#e0e0e0')}; + color: ${({ checked }) => (checked ? '#fff' : '#000')}; + cursor: pointer; + user-select: none; + font-size: 11px; +` + +const Label = styled.span` + margin-right: 10px; +` + +const Count = styled.span` + font-weight: bold; +` + +export const ToggleFilter: React.FC = ({ checked, onChange, label, count }) => { + return ( + + + {count} + + ) +} From b0804085d1b8c9f8c89f4a54ccc75fa41b79f513 Mon Sep 17 00:00:00 2001 From: Anxo Rodriguez Date: Mon, 16 Dec 2024 20:47:27 +0000 Subject: [PATCH 3/9] feat: filter in the client side for canceled/expired orders --- .../orders/OrdersUserDetailsTable/index.tsx | 125 ++++++++++++++---- 1 file changed, 102 insertions(+), 23 deletions(-) diff --git a/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx b/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx index 4677fa576f..268ce54697 100644 --- a/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx +++ b/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx @@ -17,13 +17,17 @@ import { useNetworkId } from 'state/network' import styled from 'styled-components/macro' import { FormatAmountPrecision, formattedAmount } from 'utils' -import { Order } from 'api/operator' +import { Order, OrderStatus } from 'api/operator' import { getLimitPrice } from 'utils/getLimitPrice' import { OrderSurplusDisplayStyledByRow } from './OrderSurplusTooltipStyledByRow' import { SimpleTable, SimpleTableProps } from '../../common/SimpleTable' import { StatusLabel } from '../StatusLabel' +import { ToggleFilter } from './ToggleFilter' +import { UnsignedOrderWarning } from '../UnsignedOrderWarning' + +const EXPIRED_CANCELED_STATES: OrderStatus[] = ['cancelled', 'cancelling', 'expired'] const tooltip = { orderID: 'A unique identifier ID for this order.', @@ -46,9 +50,26 @@ export type Props = SimpleTableProps & { interface RowProps { order: Order isPriceInverted: boolean + + // TODO: Filter by state using the API. Not available for now, so filtering in the client + showCanceledAndExpired: boolean + showPreSigning: boolean } -const RowOrder: React.FC = ({ order, isPriceInverted }) => { +const FilterRow = styled.tr` + background-color: ${({ theme }) => theme.background}; + + th { + text-align: right; + padding-right: 10px; + + & > * { + margin-left: 10px; + } + } +` + +const RowOrder: React.FC = ({ order, isPriceInverted, showCanceledAndExpired, showPreSigning }) => { const { creationDate, buyToken, buyAmount, sellToken, sellAmount, kind, partiallyFilled, uid, filledPercentage } = order const [_isPriceInverted, setIsPriceInverted] = useState(isPriceInverted) @@ -67,6 +88,10 @@ const RowOrder: React.FC = ({ order, isPriceInverted }) => { if (textValue === '-') return } + // Hide the row if the order is canceled, expired or pre-signing + if (!showCanceledAndExpired && EXPIRED_CANCELED_STATES.includes(order.status)) return null + if (!showPreSigning && order.status === 'signing') return null + return ( @@ -118,6 +143,18 @@ const RowOrder: React.FC = ({ order, isPriceInverted }) => { const OrdersUserDetailsTable: React.FC = (props) => { const { orders, messageWhenEmpty } = props const [isPriceInverted, setIsPriceInverted] = useState(false) + const [showCanceledAndExpired, setShowCanceledAndExpired] = useState(false) + const [showPreSigning, setShowPreSigning] = useState(false) + + const canceledAndExpiredCount = orders + ? orders.filter((order) => EXPIRED_CANCELED_STATES.includes(order.status)).length + : 0 + const preSigningCount = orders ? orders.filter((order) => order.status === 'signing').length : 0 + const showFilter = canceledAndExpiredCount > 0 || preSigningCount > 0 + const allOrdersAreHidden = + orders?.length === (showPreSigning ? 0 : preSigningCount) + (showCanceledAndExpired ? 0 : canceledAndExpiredCount) + + console.log('allOrdersAreHidden', { allOrdersAreHidden, preSigningCount, canceledAndExpiredCount }) const invertLimitPrice = (): void => { setIsPriceInverted((previousValue) => !previousValue) @@ -130,30 +167,72 @@ const OrdersUserDetailsTable: React.FC = (props) => { return ( - - - Order ID - - - Type - Sell amount - Buy amount - - - Limit price - - - Surplus - Created - Status - + <> + {showFilter && ( + + + {canceledAndExpiredCount > 0 && ( + setShowCanceledAndExpired((previousValue) => !previousValue)} + label={(showCanceledAndExpired ? 'Hide' : 'Show') + ' canceled/expired'} + count={canceledAndExpiredCount} + /> + )} + {preSigningCount > 0 && ( + <> + setShowPreSigning((previousValue) => !previousValue)} + label={(showPreSigning ? 'Hide' : 'Show') + ' unsigned'} + count={preSigningCount} + /> + {showPreSigning && } + + )} + + + )} + {!allOrdersAreHidden && ( + + + + Order ID + + + Type + Sell amount + Buy amount + + + Limit price + + + Surplus + Created + Status + + )} + } body={ <> - {orders.map((item) => ( - - ))} + {!allOrdersAreHidden ? ( + orders.map((item) => ( + + )) + ) : ( + <> +

No orders found.

+

You can toggle the filters to show the {orders.length} hidden orders.

+ + )} } /> From 884e26c01015003f32df35d0460961e30e49d491 Mon Sep 17 00:00:00 2001 From: Anxo Rodriguez Date: Mon, 16 Dec 2024 20:49:14 +0000 Subject: [PATCH 4/9] feat: show warning --- .../components/orders/DetailsTable/index.tsx | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/explorer/src/components/orders/DetailsTable/index.tsx b/apps/explorer/src/components/orders/DetailsTable/index.tsx index 08756c8a4c..a52aaa8af1 100644 --- a/apps/explorer/src/components/orders/DetailsTable/index.tsx +++ b/apps/explorer/src/components/orders/DetailsTable/index.tsx @@ -3,7 +3,7 @@ import React from 'react' import { ExplorerDataType, getExplorerLink } from '@cowprotocol/common-utils' import { SupportedChainId } from '@cowprotocol/cow-sdk' import { Command } from '@cowprotocol/types' -import { Media } from '@cowprotocol/ui' +import { Icon, Media, UI } from '@cowprotocol/ui' import { TruncatedText } from '@cowprotocol/ui/pure/TruncatedText' import { faFill, faGroupArrowsRotate, faHistory, faProjectDiagram } from '@fortawesome/free-solid-svg-icons' @@ -31,6 +31,7 @@ import { Order } from 'api/operator' import { getUiOrderType } from 'utils/getUiOrderType' import { OrderHooksDetails } from '../OrderHooksDetails' +import { UnsignedOrderWarning } from '../UnsignedOrderWarning' const tooltip = { orderID: 'A unique identifier ID for this order.', @@ -87,6 +88,8 @@ const tooltip = { } export const Wrapper = styled.div` + --cow-color-alert: ${({ theme }): string => theme.alert2}; + display: flex; flex-direction: row; @@ -126,6 +129,10 @@ export const LinkButton = styled(LinkWithPrefixNetwork)` } ` +const WarningRow = styled.tr` + background-color: ${({ theme }): string => theme.background}; +` + export type Props = { chainId: SupportedChainId order: Order @@ -167,12 +174,20 @@ export function DetailsTable(props: Props): React.ReactNode | null { } const onCopy = (label: string): void => clickOnOrderDetails('Copy', label) + const isSigning = status === 'signing' return ( + {isSigning && ( + + + + + + )} @@ -195,6 +210,12 @@ export function DetailsTable(props: Props): React.ReactNode | null { + {isSigning && ( + <> + +   + + )} onCopy('ownerAddress')} From 5a418cac7d30f875aa7f4a4b8f27b4f2128c7a87 Mon Sep 17 00:00:00 2001 From: Anxo Rodriguez Date: Mon, 16 Dec 2024 22:20:49 +0000 Subject: [PATCH 5/9] feat: style the no orders container --- .../orders/OrdersUserDetailsTable/index.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx b/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx index 268ce54697..9acc207b16 100644 --- a/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx +++ b/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx @@ -69,6 +69,15 @@ const FilterRow = styled.tr` } ` +const NoOrdersContainer = styled.div` + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: center; + flex-direction: column; + padding: 2rem; +` + const RowOrder: React.FC = ({ order, isPriceInverted, showCanceledAndExpired, showPreSigning }) => { const { creationDate, buyToken, buyAmount, sellToken, sellAmount, kind, partiallyFilled, uid, filledPercentage } = order @@ -228,10 +237,10 @@ const OrdersUserDetailsTable: React.FC = (props) => { /> )) ) : ( - <> +

No orders found.

You can toggle the filters to show the {orders.length} hidden orders.

- +
)} } From bde66c5b4cfef17c28a34cf3953c34b50e07c9ed Mon Sep 17 00:00:00 2001 From: Anxo Rodriguez Date: Mon, 16 Dec 2024 23:57:25 +0000 Subject: [PATCH 6/9] fix: fix lint --- .../components/orders/OrdersUserDetailsTable/ToggleFilter.tsx | 3 ++- .../src/components/orders/OrdersUserDetailsTable/index.tsx | 2 +- .../src/components/orders/UnsignedOrderWarning/index.tsx | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/explorer/src/components/orders/OrdersUserDetailsTable/ToggleFilter.tsx b/apps/explorer/src/components/orders/OrdersUserDetailsTable/ToggleFilter.tsx index 03eca693b8..64c0691ead 100644 --- a/apps/explorer/src/components/orders/OrdersUserDetailsTable/ToggleFilter.tsx +++ b/apps/explorer/src/components/orders/OrdersUserDetailsTable/ToggleFilter.tsx @@ -1,5 +1,6 @@ import React from 'react' -import styled from 'styled-components' + +import styled from 'styled-components/macro' interface BadgeProps { checked: boolean diff --git a/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx b/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx index 9acc207b16..b346329bdc 100644 --- a/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx +++ b/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx @@ -21,10 +21,10 @@ import { Order, OrderStatus } from 'api/operator' import { getLimitPrice } from 'utils/getLimitPrice' import { OrderSurplusDisplayStyledByRow } from './OrderSurplusTooltipStyledByRow' +import { ToggleFilter } from './ToggleFilter' import { SimpleTable, SimpleTableProps } from '../../common/SimpleTable' import { StatusLabel } from '../StatusLabel' -import { ToggleFilter } from './ToggleFilter' import { UnsignedOrderWarning } from '../UnsignedOrderWarning' const EXPIRED_CANCELED_STATES: OrderStatus[] = ['cancelled', 'cancelling', 'expired'] diff --git a/apps/explorer/src/components/orders/UnsignedOrderWarning/index.tsx b/apps/explorer/src/components/orders/UnsignedOrderWarning/index.tsx index ef5a8addef..06e2e5a566 100644 --- a/apps/explorer/src/components/orders/UnsignedOrderWarning/index.tsx +++ b/apps/explorer/src/components/orders/UnsignedOrderWarning/index.tsx @@ -1,5 +1,6 @@ import { BannerOrientation, InlineBanner } from '@cowprotocol/ui' -import styled from 'styled-components' + +import styled from 'styled-components/macro' const StyledInlineBanner = styled(InlineBanner)` --cow-color-danger-text: ${({ theme }): string => theme.alert2}; From 9ddddcefbb335e3deb277e6c6eaa9851cb6748c6 Mon Sep 17 00:00:00 2001 From: Anxo Rodriguez Date: Tue, 17 Dec 2024 10:09:02 +0000 Subject: [PATCH 7/9] chore: delete debug line --- .../src/components/orders/OrdersUserDetailsTable/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx b/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx index b346329bdc..2ac33da81d 100644 --- a/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx +++ b/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx @@ -163,8 +163,6 @@ const OrdersUserDetailsTable: React.FC = (props) => { const allOrdersAreHidden = orders?.length === (showPreSigning ? 0 : preSigningCount) + (showCanceledAndExpired ? 0 : canceledAndExpiredCount) - console.log('allOrdersAreHidden', { allOrdersAreHidden, preSigningCount, canceledAndExpiredCount }) - const invertLimitPrice = (): void => { setIsPriceInverted((previousValue) => !previousValue) } From ba214f335e3da28687e7ed9db36cb28b7a5555fe Mon Sep 17 00:00:00 2001 From: Anxo Rodriguez Date: Tue, 17 Dec 2024 13:29:29 +0000 Subject: [PATCH 8/9] fix: show partially fillable orders --- .../orders/OrdersUserDetailsTable/index.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx b/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx index 2ac33da81d..7418b99cf2 100644 --- a/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx +++ b/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx @@ -29,6 +29,15 @@ import { UnsignedOrderWarning } from '../UnsignedOrderWarning' const EXPIRED_CANCELED_STATES: OrderStatus[] = ['cancelled', 'cancelling', 'expired'] +function isExpiredOrCanceled(order: Order): boolean { + const { executedSellAmount, executedBuyAmount, status } = order + // We don't consider an order expired or canceled if it was partially or fully filled + if (!executedSellAmount.isZero() || !executedBuyAmount.isZero()) return false + + // Otherwise, return if the order is expired or canceled + return EXPIRED_CANCELED_STATES.includes(order.status) +} + const tooltip = { orderID: 'A unique identifier ID for this order.', } @@ -98,7 +107,7 @@ const RowOrder: React.FC = ({ order, isPriceInverted, showCanceledAndE } // Hide the row if the order is canceled, expired or pre-signing - if (!showCanceledAndExpired && EXPIRED_CANCELED_STATES.includes(order.status)) return null + if (!showCanceledAndExpired && isExpiredOrCanceled(order)) return null if (!showPreSigning && order.status === 'signing') return null return ( @@ -155,9 +164,7 @@ const OrdersUserDetailsTable: React.FC = (props) => { const [showCanceledAndExpired, setShowCanceledAndExpired] = useState(false) const [showPreSigning, setShowPreSigning] = useState(false) - const canceledAndExpiredCount = orders - ? orders.filter((order) => EXPIRED_CANCELED_STATES.includes(order.status)).length - : 0 + const canceledAndExpiredCount = orders ? orders.filter(isExpiredOrCanceled).length : 0 const preSigningCount = orders ? orders.filter((order) => order.status === 'signing').length : 0 const showFilter = canceledAndExpiredCount > 0 || preSigningCount > 0 const allOrdersAreHidden = From d873feba0535801b8a7345e4059bc18cd80b7bf4 Mon Sep 17 00:00:00 2001 From: Anxo Rodriguez Date: Tue, 17 Dec 2024 15:01:20 +0000 Subject: [PATCH 9/9] chore: simplify Co-authored-by: Leandro --- .../src/components/orders/OrdersUserDetailsTable/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx b/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx index 7418b99cf2..935442b1c4 100644 --- a/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx +++ b/apps/explorer/src/components/orders/OrdersUserDetailsTable/index.tsx @@ -164,8 +164,8 @@ const OrdersUserDetailsTable: React.FC = (props) => { const [showCanceledAndExpired, setShowCanceledAndExpired] = useState(false) const [showPreSigning, setShowPreSigning] = useState(false) - const canceledAndExpiredCount = orders ? orders.filter(isExpiredOrCanceled).length : 0 - const preSigningCount = orders ? orders.filter((order) => order.status === 'signing').length : 0 + const canceledAndExpiredCount = orders?.filter(isExpiredOrCanceled).length || 0 + const preSigningCount = orders?.filter((order) => order.status === 'signing').length || 0 const showFilter = canceledAndExpiredCount > 0 || preSigningCount > 0 const allOrdersAreHidden = orders?.length === (showPreSigning ? 0 : preSigningCount) + (showCanceledAndExpired ? 0 : canceledAndExpiredCount)