Skip to content

Commit

Permalink
feat(limit): recreate order (cowprotocol#3845)
Browse files Browse the repository at this point in the history
* chore: add crude recreate modal

* refactor: move state to common/state

* refactor: rename recreateOrder to alternativeOrder

* feat: use alternative limit order atoms when flag is set

* refactor: shorten local atom names

* refactor: add readWriteAtomFactory syntactic sugar

* refactor: simplify atom types

* chore: add option to set isUnlocked on default state

* chore: accept partial state

* refactor: use exposed hooks rather than atom directly

* refactor: rename useGetAlternativeOrder to useAlternativeOrder

* fix: use SELL order as default limit, no idea why I changed to BUY

* chore: do not overwrite input/out currency ids if not present

* feat: show recipient field when recipient is set in state

* feat: do not reset recipient on load when it's alternative modal

* feat: do not show app switcher when in alternative modal

* feat: do not setup trade state when alternative modal

* feat: load currency amount from atoms too

* feat: disable selectors and switched on alternative modal

* refactor: move factory utils to alternativeOrder state

* feat: use alternative state for partiallyFillableOverrideAtom

* chore: add placeholder to keep settings button on the right side

* feat: duplicate limitOrderSettings atoms

* chore: remove RecreateOrderModal components

* feat: add AlternativeLimitOrder page

* feat: display regular or alternative limit order pages according to flag

* refactor: clean up LimitOrders page

* refactor: simplify alternativeOrder state

* feat: set partialFillEnabled feature flag on if the order was partiallyFillable

* feat: move back to limit orders page after order is recreated

* fix: remove duplicated css prop z-index

* feat: use repeat icon for recreate limit order action

* refactor: use Command instead of () => void

* chore: remove TODO

* fix: add missing import

* feat: do not show recreate button for pending orders

* feat: hide alternative modal when account or chainId change

* fix: fix build issues

* refactor: split alternativeOrder index into multiple files

* feat: do not update active rate when recreating the order

* refactor: split AlternativeLimitOrderUpdater into separated hooks

* refactor: group limit order updaters with order types

* fix: recreate is only available for limit orders

* feat(limit): move to open orders after limit placed (cowprotocol#3889)

* refactor: organize imports and files on ordersTable module

* feat: add helper hook useNavitageToOpenOrdersTable

* feat: navitate to open orders table when limit order is placed

* refactor: import from modules/tokens

* test: fix unit tests

* feat(limit): add recreate button to limit order receipt (cowprotocol#3890)

* feat(limit): recreate v2 fix price (cowprotocol#3899)

* feat: do not use InitialPriceUpdated on alternative limit orders

* fix: reset state when loading to avoid stale data

* refactor: recreate v2 refactor (cowprotocol#3900)

* refactor: move alternativeOrder state from common into trade state

* chore: do not export alternativeOrderAtomGetterFactory

* fix: add missing hook dependency

* feat(limit): fix price impact on load (cowprotocol#3929)

* fix: clear existing market rate when alternative limit order form is loaded

* fix: use pure JS numbers math to avoid issue with Fraction division

* feat: recreate order styling (cowprotocol#3930)

* feat: progress

* feat: progress

* feat: progress

* feat: progress

* feat: progress

* feat: cleanup

---------

Co-authored-by: fairlight <[email protected]>
  • Loading branch information
alfetopito and fairlighteth authored Feb 29, 2024
1 parent 146c589 commit dd31431
Show file tree
Hide file tree
Showing 64 changed files with 846 additions and 240 deletions.
13 changes: 10 additions & 3 deletions apps/cowswap-frontend/src/common/pure/ButtonSecondary/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import { UI } from '@cowprotocol/ui'

import styled from 'styled-components/macro'

export const ButtonSecondary = styled.button<{ padding?: string; minHeight?: string }>`
background: var(${UI.COLOR_PRIMARY});
color: var(${UI.COLOR_BUTTON_TEXT});
export const ButtonSecondary = styled.button<{
padding?: string
minHeight?: string
margin?: string
variant?: string
}>`
background: ${({ variant }) => (variant === 'light' ? `var(${UI.COLOR_PAPER_DARKER})` : `var(${UI.COLOR_PRIMARY})`)};
color: ${({ variant }) => (variant === 'light' ? `var(${UI.COLOR_TEXT})` : `var(${UI.COLOR_BUTTON_TEXT})`)};
font-size: 12px;
font-weight: 600;
border: 0;
Expand All @@ -16,8 +21,10 @@ export const ButtonSecondary = styled.button<{ padding?: string; minHeight?: str
padding: ${({ padding = '0 12px' }) => padding};
cursor: pointer;
white-space: nowrap;
margin: ${({ margin = '0' }) => margin};
&:hover {
background: var(${UI.COLOR_PRIMARY_LIGHTER});
color: var(${UI.COLOR_BUTTON_TEXT});
}
`
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,29 @@ import * as styledEl from './styled'

export interface CurrencyArrowSeparatorProps {
isLoading: boolean
disabled?: boolean
withRecipient: boolean
hasSeparatorLine?: boolean
isCollapsed?: boolean
onSwitchTokens(): void
border?: boolean
}

export function CurrencyArrowSeparator(props: CurrencyArrowSeparatorProps) {
const { isLoading, onSwitchTokens, withRecipient, isCollapsed = true, hasSeparatorLine } = props
const { isLoading, onSwitchTokens, withRecipient, isCollapsed = true, hasSeparatorLine, disabled = false } = props
const isInjectedWidgetMode = isInjectedWidget()

return (
<styledEl.Box withRecipient={withRecipient} isCollapsed={isCollapsed} hasSeparatorLine={hasSeparatorLine}>
<styledEl.Box
withRecipient={withRecipient}
isCollapsed={isCollapsed}
hasSeparatorLine={hasSeparatorLine}
disabled={disabled}
>
<styledEl.LoadingWrapper isLoading={isLoading}>
{!isInjectedWidgetMode && isLoading ? (
<styledEl.CowImg src={loadingCowWebp} alt="loading" />
) : (
<styledEl.ArrowDownIcon onClick={onSwitchTokens} />
<styledEl.ArrowDownIcon onClick={disabled ? undefined : onSwitchTokens} disabled={disabled} />
)}
</styledEl.LoadingWrapper>
</styledEl.Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import styled, { css } from 'styled-components/macro'

import { loadingAnimationMixin } from './style-mixins'

export const Box = styled.div<{ withRecipient: boolean; isCollapsed: boolean; hasSeparatorLine?: boolean }>`
export const Box = styled.div<{
withRecipient: boolean
isCollapsed: boolean
hasSeparatorLine?: boolean
disabled: boolean
}>`
display: ${({ withRecipient }) => (withRecipient ? 'inline-flex' : 'block')};
margin: ${({ withRecipient, isCollapsed }) => (withRecipient ? '0' : isCollapsed ? '-13px auto' : '2px auto')};
cursor: pointer;
cursor: ${({ disabled }) => (disabled ? 'inherit' : 'pointer')};
color: inherit;
position: relative;
z-index: 2;
Expand Down Expand Up @@ -58,15 +63,15 @@ export const LoadingWrapper = styled.div<{ isLoading: boolean }>`
${({ isLoading }) => isLoading && loadingAnimationMixin}
`

export const ArrowDownIcon = styled(ArrowDown)`
export const ArrowDownIcon = styled(ArrowDown)<{ disabled: boolean }>`
display: block;
margin: auto;
stroke: currentColor;
stroke-width: 3px;
padding: 0;
height: 100%;
width: 20px;
cursor: pointer;
cursor: ${({ disabled }) => (disabled ? 'inherit' : 'pointer')};
color: inherit;
`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { setMaxSellTokensAnalytics } from '@cowprotocol/analytics'
import { NATIVE_CURRENCIES } from '@cowprotocol/common-const'
import { formatInputAmount, getIsNativeToken } from '@cowprotocol/common-utils'
import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { TokenAmount } from '@cowprotocol/ui'
import { MouseoverTooltip } from '@cowprotocol/ui'
import { MouseoverTooltip, TokenAmount } from '@cowprotocol/ui'
import { Currency, CurrencyAmount } from '@uniswap/sdk-core'

import { Trans } from '@lingui/macro'
Expand Down Expand Up @@ -34,6 +33,7 @@ export interface CurrencyInputPanelProps extends Partial<BuiltItProps> {
isChainIdUnsupported: boolean
disabled?: boolean
inputDisabled?: boolean
tokenSelectorDisabled?: boolean
inputTooltip?: string
showSetMax?: boolean
maxBalance?: CurrencyAmount<Currency> | undefined
Expand All @@ -58,6 +58,7 @@ export function CurrencyInputPanel(props: CurrencyInputPanelProps) {
showSetMax = false,
maxBalance,
inputDisabled = false,
tokenSelectorDisabled = false,
inputTooltip,
onUserInput,
allowsOffchainSigning,
Expand Down Expand Up @@ -153,6 +154,7 @@ export function CurrencyInputPanel(props: CurrencyInputPanelProps) {
}
currency={disabled ? undefined : currency || undefined}
loading={areCurrenciesLoading || disabled}
readonlyMode={tokenSelectorDisabled}
/>
</div>
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const CurrencySelectWrapper = styled.button<{ isLoading: boolean; $stubbe
pointer-events: ${({ readonlyMode }) => (readonlyMode ? 'none' : '')};
border-radius: var(${UI.BORDER_RADIUS_NORMAL});
padding: 6px;
${({ readonlyMode }) => (readonlyMode ? 'padding-right: 10px;' : '')}
transition: background var(${UI.ANIMATION_DURATION}) ease-in-out, color var(${UI.ANIMATION_DURATION}) ease-in-out;
max-width: 190px;
Expand Down
47 changes: 29 additions & 18 deletions apps/cowswap-frontend/src/common/pure/NewModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ const ModalInner = styled.div`
position: relative;
`

const Wrapper = styled.div<{ maxWidth?: number | string; minHeight?: number | string }>`
const Wrapper = styled.div<{
maxWidth?: number | string
minHeight?: number | string
modalMode?: boolean
}>`
display: flex;
width: 100%;
height: 100%;
margin: auto;
overflow-y: auto;
background: var(${UI.COLOR_PAPER});
background: ${({ modalMode }) => (modalMode ? `var(${UI.COLOR_PAPER_DARKER})` : `var(${UI.COLOR_PAPER})`)};
border: ${({ modalMode }) => (modalMode ? `1px solid var(${UI.COLOR_PAPER})` : 'none')};
border-radius: var(${UI.BORDER_RADIUS_NORMAL});
box-shadow: var(${UI.BOX_SHADOW});
Expand All @@ -49,11 +54,12 @@ const Wrapper = styled.div<{ maxWidth?: number | string; minHeight?: number | st

const Heading = styled.h2`
display: flex;
flex-flow: row wrap;
justify-content: space-between;
width: 100%;
height: auto;
margin: 0;
padding: 18px 40px;
padding: 16px 20px 3px;
font-size: var(${UI.FONT_SIZE_MEDIUM});
${({ theme }) => theme.mediaWidth.upToSmall`
Expand All @@ -63,13 +69,10 @@ const Heading = styled.h2`
`

const IconX = styled.div`
position: absolute;
top: 16px;
right: 10px;
cursor: pointer;
opacity: 0.7;
transition: opacity var(${UI.ANIMATION_DURATION}) ease-in-out;
margin: 0 0 0 auto;
margin: 0;
> svg {
width: var(${UI.ICON_SIZE_NORMAL});
Expand All @@ -88,23 +91,27 @@ const BackButtonStyled = styled(BackButton)`
left: 10px;
`

const NewModalContent = styled.div<{ paddingTop?: number }>`
const NewModalContent = styled.div<{ padding?: string }>`
display: flex;
align-items: center;
justify-content: center;
flex-flow: column wrap;
flex: 1;
width: 100%;
height: 100%;
padding: 0 var(${UI.PADDING_NORMAL}) var(${UI.PADDING_NORMAL});
padding: ${({ padding }) => padding || `0 var(${UI.PADDING_NORMAL}) var(${UI.PADDING_NORMAL})`};
&.modalMode {
padding: 10px;
}
h1,
h2,
h3 {
width: 100%;
font-size: var(${UI.FONT_SIZE_LARGER});
font-size: var(${UI.FONT_SIZE_MEDIUM});
font-weight: var(${UI.FONT_WEIGHT_BOLD});
text-align: center;
text-align: left;
line-height: 1.4;
margin: 0 auto;
}
Expand Down Expand Up @@ -159,17 +166,21 @@ export function NewModal({ maxWidth = 450, minHeight = 350, modalMode, title, ch
const onDismissCallback = useCallback(() => onDismiss?.(), [onDismiss])

return (
<Wrapper maxWidth={maxWidth} minHeight={minHeight}>
<Wrapper maxWidth={maxWidth} minHeight={minHeight} modalMode={modalMode}>
<ModalInner>
{!modalMode && <BackButtonStyled onClick={onDismissCallback} />}
{title && <Heading>{title}</Heading>}
{modalMode && (
<IconX onClick={onDismissCallback}>
<SVG src={CLOSE_ICON} />
</IconX>
{title && (
<Heading>
{title}{' '}
{modalMode && (
<IconX onClick={onDismissCallback}>
<SVG src={CLOSE_ICON} />
</IconX>
)}
</Heading>
)}

<NewModalContent>{children}</NewModalContent>
<NewModalContent className={modalMode ? 'modalMode' : ''}>{children}</NewModalContent>
</ModalInner>
</Wrapper>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useAtomValue, useSetAtom } from 'jotai'
import { useAtomValue } from 'jotai'
import { useCallback } from 'react'

import { changeSwapAmountAnalytics } from '@cowprotocol/analytics'
Expand All @@ -7,8 +7,8 @@ import { OrderKind } from '@cowprotocol/cow-sdk'

import { Field } from 'legacy/state/types'

import { updateLimitOrdersRawStateAtom } from 'modules/limitOrders'
import { useLimitOrdersDerivedState } from 'modules/limitOrders/hooks/useLimitOrdersDerivedState'
import { useUpdateLimitOrdersRawState } from 'modules/limitOrders/hooks/useLimitOrdersRawState'
import { useUpdateCurrencyAmount } from 'modules/limitOrders/hooks/useUpdateCurrencyAmount'
import { limitRateAtom } from 'modules/limitOrders/state/limitRateAtom'
import { TradeWidgetActions } from 'modules/trade'
Expand All @@ -22,7 +22,7 @@ export function useLimitOrdersWidgetActions(): TradeWidgetActions {
const isWrapOrUnwrap = useIsWrapOrUnwrap()
const updateCurrencyAmount = useUpdateCurrencyAmount()

const updateLimitOrdersState = useSetAtom(updateLimitOrdersRawStateAtom)
const updateLimitOrdersState = useUpdateLimitOrdersRawState()

const onCurrencySelection = useOnCurrencySelection()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useAtomValue, useSetAtom } from 'jotai'
import { useAtomValue } from 'jotai'
import React, { useMemo } from 'react'

import { isSellOrder } from '@cowprotocol/common-utils'
Expand All @@ -21,9 +21,9 @@ import * as styledEl from './styled'

import { useLimitOrdersDerivedState } from '../../hooks/useLimitOrdersDerivedState'
import { LimitOrdersFormState, useLimitOrdersFormState } from '../../hooks/useLimitOrdersFormState'
import { useUpdateLimitOrdersRawState } from '../../hooks/useLimitOrdersRawState'
import { useTradeFlowContext } from '../../hooks/useTradeFlowContext'
import { InfoBanner } from '../../pure/InfoBanner'
import { updateLimitOrdersRawStateAtom } from '../../state/limitOrdersRawStateAtom'
import { limitOrdersSettingsAtom } from '../../state/limitOrdersSettingsAtom'
import { limitRateAtom } from '../../state/limitRateAtom'
import { DeadlineInput } from '../DeadlineInput'
Expand Down Expand Up @@ -77,7 +77,8 @@ export function LimitOrdersWidget() {
const rateInfoParams = useRateInfoParams(inputCurrencyAmount, outputCurrencyAmount)
const widgetActions = useLimitOrdersWidgetActions()

const { showRecipient } = settingsState
const { showRecipient: showRecipientSetting } = settingsState
const showRecipient = showRecipientSetting || !!recipient

const priceImpact = useTradePriceImpact()
const quoteAmount = useMemo(
Expand Down Expand Up @@ -157,7 +158,7 @@ const LimitOrders = React.memo((props: LimitOrdersProps) => {
return isRateLoading
}, [isRateLoading, inputCurrency, outputCurrency])

const updateLimitOrdersState = useSetAtom(updateLimitOrdersRawStateAtom)
const updateLimitOrdersState = useUpdateLimitOrdersRawState()

const inputCurrencyPreviewInfo = {
amount: inputCurrencyInfo.amount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useAtomValue, useSetAtom } from 'jotai'
import { useCallback, useEffect, useMemo, useState } from 'react'

import { formatInputAmount, getAddress, isFractionFalsy } from '@cowprotocol/common-utils'
import { TokenSymbol, Loader } from '@cowprotocol/ui'
import { Loader, TokenSymbol } from '@cowprotocol/ui'
import { useWalletInfo } from '@cowprotocol/wallet'

import { RefreshCw } from 'react-feather'
Expand Down Expand Up @@ -70,6 +70,7 @@ export function RateInput() {
activeRate: isFractionFalsy(marketRate) ? initialRate : marketRate,
isTypedValue: false,
isRateFromUrl: false,
isAlternativeOrderRate: false,
})
}, [marketRate, initialRate, updateRate])

Expand All @@ -81,6 +82,7 @@ export function RateInput() {
activeRate: toFraction(typedValue, isInverted),
isTypedValue: true,
isRateFromUrl: false,
isAlternativeOrderRate: false,
})
},
[isInverted, updateRate, updateLimitRateState]
Expand Down
Loading

0 comments on commit dd31431

Please sign in to comment.