Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
10de804
chore: mark places where order summary is in use
shoom3301 Dec 25, 2025
405295e
refactor: simplify createActivityDescriptor
shoom3301 Dec 25, 2025
448ac0e
refactor(ActivityDetails): replace order.summary with computeOrderSum…
shoom3301 Dec 25, 2025
9fad34a
refactor(RequestCancellationModal): extract styled el to another file
shoom3301 Dec 25, 2025
533ac14
refactor(CancellationModal): use computeOrderSummary instead of order…
shoom3301 Dec 25, 2025
c7962be
refactor(RequestCancellationModal): get rid of render functions
shoom3301 Dec 24, 2025
cf33725
refactor: simplify computeOrderSummary
shoom3301 Dec 24, 2025
1ebc832
refactor: extract useGetSerializedBridgeOrder
shoom3301 Dec 24, 2025
0c70318
feat: support all kinds of order in computeOrderSummary
shoom3301 Dec 24, 2025
8f4a80e
feat: compute order summary taking bridge into account
shoom3301 Dec 25, 2025
9334831
fix: fix canceled bridge order displaying
shoom3301 Dec 25, 2025
f318fb9
chore: fix tests
shoom3301 Dec 25, 2025
50d1c28
chore: add tests for bridging orders
shoom3301 Dec 25, 2025
f4b727a
chore: simplify types
shoom3301 Dec 25, 2025
b9b5450
chore: remove excessive computeOrderSummary call
shoom3301 Dec 26, 2025
94df4d2
refactor: replace computeOrderSummary with OrderSummary
shoom3301 Dec 26, 2025
374b0b3
fix: generalize OrderNotification for any type of order
shoom3301 Dec 29, 2025
cbafb71
chore: remove dead code
shoom3301 Dec 29, 2025
279b507
Merge branch 'develop' into refactor/order-summary
shoom3301 Dec 30, 2025
6e0827c
Merge branch 'refactor/order-summary' of https://github.com/cowprotoc…
shoom3301 Dec 30, 2025
75a02ed
Merge branch 'develop' of https://github.com/cowprotocol/cowswap into…
shoom3301 Dec 30, 2025
93b8d91
Merge branch 'refactor/order-summary-1' of https://github.com/cowprot…
shoom3301 Dec 30, 2025
19c9474
Merge branch 'develop' of https://github.com/cowprotocol/cowswap into…
shoom3301 Dec 30, 2025
9771cea
Merge branch 'develop' of https://github.com/cowprotocol/cowswap into…
shoom3301 Dec 30, 2025
f8962f8
chore: update i18n
shoom3301 Dec 30, 2025
f1f7a1a
chore: delete irrelevant tests
shoom3301 Dec 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { useAtomValue } from 'jotai'
import { ReactNode } from 'react'
import React, { ReactNode, useMemo } from 'react'

import { Command } from '@cowprotocol/types'

import { cancellationModalContextAtom } from 'common/hooks/useCancelOrder/state'
import { CancellationModal as Pure } from 'common/pure/CancellationModal'
import { OrderSummary } from 'common/pure/OrderSummary'

import { useUltimateOrder } from '../../hooks/useUltimateOrder'
import { getUltimateOrderTradeAmounts } from '../../updaters/orders/utils'

export type CancellationModalProps = {
isOpen: boolean
Expand All @@ -15,6 +19,17 @@ export function CancellationModal(props: CancellationModalProps): ReactNode {
const { isOpen, onDismiss } = props

const context = useAtomValue(cancellationModalContextAtom)
const ultimateOrder = useUltimateOrder(context.chainId || undefined, context.orderId || undefined)

const orderSummary = useMemo(() => {
if (!ultimateOrder) return undefined

const { inputAmount, outputAmount } = getUltimateOrderTradeAmounts(ultimateOrder)

return (
<OrderSummary inputAmount={inputAmount} outputAmount={outputAmount} kind={ultimateOrder.orderFromStore.kind} />
)
}, [ultimateOrder])

return <Pure isOpen={isOpen} onDismiss={onDismiss} context={context} />
return <Pure isOpen={isOpen} onDismiss={onDismiss} context={context} orderSummary={orderSummary} />
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,24 +102,20 @@ export function MultipleOrdersCancellationModal(props: Props): ReactNode {
<LegacyConfirmationModalContent
title={t`Cancel multiple orders: ${ordersCount}`}
onDismiss={onDismiss}
// TODO: Extract nested component outside render function
// eslint-disable-next-line react/no-unstable-nested-components
topContent={() => (
topContent={
<div>
<p>
<Trans>Are you sure you want to cancel {ordersCount} orders?</Trans>
</p>
</div>
)}
// TODO: Extract nested component outside render function
// eslint-disable-next-line react/no-unstable-nested-components
bottomContent={() => (
}
bottomContent={
<div>
<ButtonPrimary onClick={signAndSendCancellation}>
<Trans>Request cancellations</Trans>
</ButtonPrimary>
</div>
)}
}
/>
</Modal>
)
Expand Down
16 changes: 2 additions & 14 deletions apps/cowswap-frontend/src/common/hooks/useCancelOrder/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { useGasPrices } from 'legacy/state/gas/hooks'
import { Order, OrderStatus } from 'legacy/state/orders/actions'

import { useGetOnChainCancellation } from 'common/hooks/useCancelOrder/useGetOnChainCancellation'
import { computeOrderSummary } from 'common/updaters/orders/utils'
import { isOrderCancellable } from 'common/utils/isOrderCancellable'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'

Expand All @@ -34,8 +33,6 @@ export type UseCancelOrderReturn = Command | null
* It checks whether the order is eligible for cancellation and set's up which type of cancellation can be used (on or off-chain)
* In case the order is not eligible, it returns null. This should be used to control whether a cancel button should be displayed
*/
// TODO: Break down this large function into smaller functions

export function useCancelOrder(): (order: Order) => UseCancelOrderReturn {
const { chainId } = useWalletInfo()
const { allowsOffchainSigning } = useWalletDetails()
Expand Down Expand Up @@ -67,9 +64,7 @@ export function useCancelOrder(): (order: Order) => UseCancelOrderReturn {
}

// When dismissing the modal, close it and also reset context
// TODO: Add proper return type annotation
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const onDismiss = () => {
const onDismiss = (): void => {
closeModal()
resetContext()
}
Expand All @@ -84,9 +79,7 @@ export function useCancelOrder(): (order: Order) => UseCancelOrderReturn {
await cancelFn(order)
onDismiss()
// When done, dismiss the modal
// TODO: Replace any with proper type definitions
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
} catch (e) {
onDismiss()
if (!isPendingSignature) return

Expand All @@ -98,15 +91,10 @@ export function useCancelOrder(): (order: Order) => UseCancelOrderReturn {

// The callback returned that triggers the modal
return () => {
const summary = computeOrderSummary({
orderFromStore: order,
orderFromApi: null,
})
// Updates the cancellation context with details pertaining the order
setContext({
orderId: order.id,
chainId,
summary,
defaultType: isOffChainCancellable ? 'offChain' : 'onChain',
onDismiss,
triggerCancellation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export type CancellationType = 'offChain' | 'onChain'
export type CancellationModalContext = {
chainId: number | null
orderId: string | null
summary: string | undefined | null
error: string | null
txCost: BigNumber | null
nativeCurrency: TokenWithLogo
Expand All @@ -24,7 +23,6 @@ export type CancellationModalContext = {
const defaultCancellationModalContext: CancellationModalContext = {
chainId: null,
orderId: null,
summary: null,
error: null,
txCost: null,
nativeCurrency: MAINNET_NATIVE_CURRENCY,
Expand All @@ -44,5 +42,5 @@ export const updateCancellationModalContextAtom = atom(

return { ...prevState, ...nextState }
})
}
},
)
34 changes: 34 additions & 0 deletions apps/cowswap-frontend/src/common/hooks/useUltimateOrder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useMemo } from 'react'

import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { CrossChainOrder } from '@cowprotocol/sdk-bridging'
import { BridgeOrderData, Nullish } from '@cowprotocol/types'

import { useBridgeOrderData, useCrossChainOrder } from 'entities/bridgeOrders'

import { Order } from 'legacy/state/orders/actions'
import { useOrder } from 'legacy/state/orders/hooks'

export interface UltimateOrderData {
orderFromStore: Order
bridgeOrderFromStore?: Nullish<BridgeOrderData>
bridgeOrderFromApi?: Nullish<CrossChainOrder>
}

export function useUltimateOrder(
chainId: SupportedChainId | undefined,
orderUid: string | undefined,
): UltimateOrderData | undefined {
const orderFromStore = useOrder({ id: orderUid, chainId })
const bridgeOrderFromStore = useBridgeOrderData(orderUid)
const { data: bridgeOrderFromApi } = useCrossChainOrder(chainId, orderUid)

return useMemo(() => {
if (!orderFromStore) return undefined
return {
orderFromStore,
bridgeOrderFromStore,
bridgeOrderFromApi,
}
}, [orderFromStore, bridgeOrderFromApi, bridgeOrderFromStore])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { ReactNode, useCallback, useState } from 'react'

import { TokenAmount, UI } from '@cowprotocol/ui'
import { CurrencyAmount } from '@uniswap/sdk-core'

import { t } from '@lingui/core/macro'
import { Trans } from '@lingui/react/macro'
import { ArrowLeft, ArrowRight } from 'react-feather'
import styled from 'styled-components/macro'
import { LinkStyledButton } from 'theme'

import NotificationBanner from 'legacy/components/NotificationBanner'

import { RequestCancellationModalProps } from './types'

import { CancellationType } from '../../hooks/useCancelOrder/state'

const Wrapper = styled.div`
display: flex;
flex-flow: column wrap;
margin: 0 auto;
width: 100%;
`

const TypeButton = styled.button<{ isOnChain$: boolean }>`
display: inline-flex;
align-items: center;
justify-content: space-between;
gap: 5px;
background: ${({ isOnChain$ }) => (isOnChain$ ? `var(${UI.COLOR_INFO_BG})` : `var(${UI.COLOR_PAPER_DARKER})`)};
color: ${({ isOnChain$ }) => (isOnChain$ ? `var(${UI.COLOR_INFO_TEXT})` : 'inherit')};
padding: 4px 8px;
border-radius: 4px;
outline: none;
border: 0;
margin: 0 3px;
font-size: inherit;
cursor: pointer;

:hover {
outline: 1px solid
${({ isOnChain$ }) => (isOnChain$ ? `var(${UI.COLOR_INFO_TEXT})` : `var(${UI.COLOR_TEXT_OPACITY_25})`)};
}
`

const StyledNotificationBanner = styled(NotificationBanner)`
margin-top: 15px;
margin-bottom: 0;
box-sizing: border-box;
`

const CancellationSummary = styled.span`
padding: 12px;
margin: 0;
border-radius: 6px;
background: var(${UI.COLOR_PAPER_DARKER});
line-height: 1.6;
`

const OrderTypeDetails = styled.div`
margin: 0 0 15px 5px;
padding-left: 10px;
border-left: 3px solid var(${UI.COLOR_TEXT_OPACITY_25});

> p {
margin: 0 0 10px 0;
}

> p:last-child {
margin-bottom: 0;
}
`

interface ModalTopContentProps extends RequestCancellationModalProps {
type: CancellationType
setType: (type: CancellationType) => void
}

export function ModalTopContent(props: ModalTopContentProps): ReactNode {
const { summary, shortId, defaultType, txCost, nativeCurrency, type, setType } = props
const isOffChainCancellable = defaultType === 'offChain'

const [showMore, setShowMore] = useState(false)

const toggleShowMore = (): void => setShowMore((showMore) => !showMore)

const toggleType = useCallback(() => {
const changedToOnChain = type !== 'onChain'

setType(changedToOnChain ? 'onChain' : 'offChain')

// If OnChain type is set, then open "show more" to explain tx cost details
if (changedToOnChain) {
setShowMore(true)
}
}, [type, setType])

const isOnChainType = type === 'onChain'
const typeLabel = isOnChainType ? t`on-chain` : t`off-chain`

const txCostAmount = txCost && !txCost.isZero() ? CurrencyAmount.fromRawAmount(nativeCurrency, txCost.toString()) : ''

return (
<Wrapper>
<p>
<Trans>
Are you sure you want to cancel order <strong>{shortId}</strong>?
</Trans>
</p>
<CancellationSummary>{summary}</CancellationSummary>
<p>
<Trans>This is an</Trans>{' '}
{isOffChainCancellable ? (
<TypeButton isOnChain$={isOnChainType} onClick={toggleType}>
<span>{typeLabel}</span> {isOnChainType ? <ArrowLeft size="15" /> : <ArrowRight size="15" />}
</TypeButton>
) : (
typeLabel
)}{' '}
<Trans>cancellation</Trans>{' '}
<LinkStyledButton onClick={toggleShowMore}>[{showMore ? `- ` + t`less` : `+ ` + t`more`}]</LinkStyledButton>
</p>
{showMore && (
<OrderTypeDetails>
<p>
{type === 'onChain' ? (
<Trans>On-chain cancellations require a regular on-chain transaction and cost gas.</Trans>
) : (
<Trans>Off-chain cancellations require a signature and are free.</Trans>
)}
</p>
<p>
<Trans>
Keep in mind a solver might already have included the order in a solution even if this cancellation is
successful.
</Trans>
{isOnChainType && (
<StyledNotificationBanner isVisible={true} canClose={false} level="INFO">
<div>
<Trans>Tx cost:</Trans>{' '}
{txCostAmount ? <TokenAmount amount={txCostAmount} tokenSymbol={nativeCurrency} /> : t`Unknown`}
</div>
</StyledNotificationBanner>
)}
</p>
</OrderTypeDetails>
)}
</Wrapper>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { BigNumber } from '@ethersproject/bignumber'

import { MAINNET_NATIVE_CURRENCY } from 'lib/hooks/useNativeCurrency'

import { RequestCancellationModal, RequestCancellationModalProps } from './RequestCancellationModal'
import { RequestCancellationModal } from './RequestCancellationModal'
import { RequestCancellationModalProps } from './types'

const props: Omit<RequestCancellationModalProps, 'defaultType'> = {
triggerCancellation(): void {
Expand Down
Loading