Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit UI improvement 1 #5164

Draft
wants to merge 15 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Expand Up @@ -163,6 +163,15 @@ export function CurrencyInputPanel(props: CurrencyInputPanelProps) {

{topContent}
<styledEl.CurrencyInputBox>
<div>
{inputTooltip ? (
<HoverTooltip wrapInContainer content={inputTooltip}>
{numericalInput}
</HoverTooltip>
) : (
numericalInput
)}
</div>
<div>
<CurrencySelectButton
onClick={onTokenSelectClick}
Expand All @@ -173,35 +182,26 @@ export function CurrencyInputPanel(props: CurrencyInputPanelProps) {
customSelectTokenButton={customSelectTokenButton}
/>
</div>
<div>
{inputTooltip ? (
<HoverTooltip wrapInContainer content={inputTooltip}>
{numericalInput}
</HoverTooltip>
) : (
numericalInput
)}
</div>
</styledEl.CurrencyInputBox>

<styledEl.CurrencyInputBox>
<div>
{amount && (
<styledEl.FiatAmountText>
<FiatValue priceImpactParams={priceImpactParams} fiatValue={fiatAmount} />
</styledEl.FiatAmountText>
)}
</div>
<div>
{balance && !disabled && (
<styledEl.BalanceText>
<Trans>Balance</Trans>: <TokenAmount amount={balance} defaultValue="0" tokenSymbol={currency} />
<TokenAmount amount={balance} defaultValue="0" tokenSymbol={currency} />
{showSetMax && balance.greaterThan(0) && (
<styledEl.SetMaxBtn onClick={handleMaxInput}>Max</styledEl.SetMaxBtn>
)}
</styledEl.BalanceText>
)}
</div>
<div>
{amount && (
<styledEl.FiatAmountText>
<FiatValue priceImpactParams={priceImpactParams} fiatValue={fiatAmount} />
</styledEl.FiatAmountText>
)}
</div>
</styledEl.CurrencyInputBox>
</styledEl.Wrapper>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export const NumericalInput = styled(Input)<{ $loading: boolean }>`
font-size: 28px;
font-weight: 500;
color: inherit;
text-align: left;

&::placeholder {
opacity: 0.7;
Expand Down Expand Up @@ -150,7 +151,9 @@ export const SetMaxBtn = styled.button`
border-radius: 6px;
padding: 3px 4px;
text-transform: uppercase;
transition: background var(${UI.ANIMATION_DURATION}) ease-in-out, color var(${UI.ANIMATION_DURATION}) ease-in-out;
transition:
background var(${UI.ANIMATION_DURATION}) ease-in-out,
color var(${UI.ANIMATION_DURATION}) ease-in-out;

&:hover {
background: var(${UI.COLOR_PRIMARY});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export function LimitOrdersWidget() {

const inputCurrencyInfo: CurrencyInfo = {
field: Field.INPUT,
label: isSell ? 'Sell amount' : 'You sell at most',
label: isSell ? 'Sell' : 'You sell at most',
currency: inputCurrency,
amount: inputCurrencyAmount,
isIndependent: isSell,
Expand Down Expand Up @@ -176,7 +176,7 @@ const LimitOrders = React.memo((props: LimitOrdersProps) => {
handleUnlock={() => updateLimitOrdersState({ isUnlocked: true })}
/>
),
middleContent: (
topContent: (
<>
{!isWrapOrUnwrap &&
ClosableBanner(ZERO_BANNER_STORAGE_KEY, (onClose) => (
Expand All @@ -194,17 +194,33 @@ const LimitOrders = React.memo((props: LimitOrdersProps) => {
</p>
</InlineBanner>
))}
<styledEl.RateWrapper>
<RateInput />
<DeadlineInput />
</styledEl.RateWrapper>
{props.settingsState.limitPricePosition === 'top' && (
<styledEl.RateWrapper>
<RateInput />
</styledEl.RateWrapper>
)}
</>
),
middleContent: (
<>
{props.settingsState.limitPricePosition === 'between' && (
<styledEl.RateWrapper>
<RateInput />
</styledEl.RateWrapper>
)}
</>
),
bottomContent(warnings) {
return (
<>
{props.settingsState.limitPricePosition === 'bottom' && (
<styledEl.RateWrapper>
<RateInput />
</styledEl.RateWrapper>
)}
<styledEl.FooterBox>
<TradeRateDetails rateInfoParams={rateInfoParams} />
<DeadlineInput />
<TradeRateDetails rateInfoParams={rateInfoParams} alwaysExpanded={true} />
</styledEl.FooterBox>

<LimitOrdersWarnings feeAmount={feeAmount} />
Expand All @@ -220,7 +236,7 @@ const LimitOrders = React.memo((props: LimitOrdersProps) => {
}

const params = {
compactView: false,
compactView: true,
recipient,
showRecipient,
isTradePriceUpdating: isRateLoading,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { Media } from '@cowprotocol/ui'
import { Media, UI } from '@cowprotocol/ui'

import styled from 'styled-components/macro'

import { NumericalInput } from 'modules/limitOrders/containers/RateInput/styled'

export const TradeButtonBox = styled.div`
margin: 10px 0 0;
display: flex;
Expand All @@ -20,20 +18,18 @@ export const FooterBox = styled.div`
`

export const RateWrapper = styled.div`
display: grid;
grid-template-columns: auto 151px;
grid-template-rows: max-content;
display: flex;
flex-flow: column wrap;
width: 100%;
max-width: 100%;
gap: 6px;
text-align: right;
color: inherit;
background: var(${UI.COLOR_PAPER_DARKER});
border-radius: 16px;

${Media.upToSmall()} {
display: flex;
flex-flow: column wrap;
}

${NumericalInput} {
font-size: 21px;
}
`
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import { useAtomValue, useSetAtom } from 'jotai'
import { useCallback, useEffect, useMemo, useState } from 'react'

import LockedIcon from '@cowprotocol/assets/images/icon-locked.svg'
import UnlockedIcon from '@cowprotocol/assets/images/icon-unlocked.svg'
import UsdIcon from '@cowprotocol/assets/images/icon-USD.svg'
import { formatInputAmount, getAddress, isFractionFalsy } from '@cowprotocol/common-utils'
import { HelpTooltip, Loader, TokenSymbol } from '@cowprotocol/ui'
import { TokenLogo } from '@cowprotocol/tokens'
import { TokenSymbol, HoverTooltip, HelpTooltip } from '@cowprotocol/ui'
import { useWalletInfo } from '@cowprotocol/wallet'

import { RefreshCw } from 'react-feather'

import SVG from 'react-inlinesvg'

import { useLimitOrdersDerivedState } from 'modules/limitOrders/hooks/useLimitOrdersDerivedState'
import { useRateImpact } from 'modules/limitOrders/hooks/useRateImpact'
import { useUpdateActiveRate } from 'modules/limitOrders/hooks/useUpdateActiveRate'
import { ExecutionPriceTooltip } from 'modules/limitOrders/pure/ExecutionPriceTooltip'
import { HeadingText } from 'modules/limitOrders/pure/RateInput/HeadingText'
import { executionPriceAtom } from 'modules/limitOrders/state/executionPriceAtom'
import {
limitOrdersSettingsAtom,
updateLimitOrdersSettingsAtom,
} from 'modules/limitOrders/state/limitOrdersSettingsAtom'
import { limitRateAtom, updateLimitRateAtom } from 'modules/limitOrders/state/limitRateAtom'
import { toFraction } from 'modules/limitOrders/utils/toFraction'

import { ordersTableFeatures } from 'common/constants/featureFlags'
import { ExecutionPrice } from 'common/pure/ExecutionPrice'
import { getQuoteCurrency, getQuoteCurrencyByStableCoin } from 'common/services/getQuoteCurrency'

Expand All @@ -26,21 +31,15 @@ import * as styledEl from './styled'
export function RateInput() {
const { chainId } = useWalletInfo()
// Rate state
const {
isInverted,
activeRate,
isLoading,
marketRate,
feeAmount,
isLoadingMarketRate,
typedValue,
isTypedValue,
initialRate,
} = useAtomValue(limitRateAtom)
const { isInverted, activeRate, isLoading, marketRate, isLoadingMarketRate, typedValue, isTypedValue, initialRate } =
useAtomValue(limitRateAtom)
const updateRate = useUpdateActiveRate()
const updateLimitRateState = useSetAtom(updateLimitRateAtom)
const executionPrice = useAtomValue(executionPriceAtom)
const [isQuoteCurrencySet, setIsQuoteCurrencySet] = useState(false)
const [isUsdMode, setIsUsdMode] = useState(false)
const { limitPriceLocked } = useAtomValue(limitOrdersSettingsAtom)
const updateLimitOrdersSettings = useSetAtom(updateLimitOrdersSettingsAtom)

// Limit order state
const { inputCurrency, outputCurrency, inputCurrencyAmount, outputCurrencyAmount } = useLimitOrdersDerivedState()
Expand Down Expand Up @@ -84,13 +83,33 @@ export function RateInput() {
isAlternativeOrderRate: false,
})
},
[isInverted, updateRate, updateLimitRateState]
[isInverted, updateRate, updateLimitRateState],
)

// Handle toggle USD mode
const handleToggleUsdMode = useCallback(() => {
setIsUsdMode(!isUsdMode)
}, [isUsdMode])

// Handle toggle primary field
const handleToggle = useCallback(() => {
updateLimitRateState({ isInverted: !isInverted, isTypedValue: false })
}, [isInverted, updateLimitRateState])
if (isUsdMode) {
// When in USD mode, just switch to token mode without toggling tokens
setIsUsdMode(false)
} else {
// When already in token mode, toggle between tokens
updateLimitRateState({ isInverted: !isInverted, isTypedValue: false })
}
}, [isInverted, updateLimitRateState, isUsdMode])

// Handle toggle price lock
const handleTogglePriceLock = useCallback(
(event: React.MouseEvent) => {
event.stopPropagation()
updateLimitOrdersSettings({ limitPriceLocked: !limitPriceLocked })
},
[limitPriceLocked, updateLimitOrdersSettings],
)

const isDisabledMPrice = useMemo(() => {
if (isLoadingMarketRate) return true
Expand Down Expand Up @@ -144,13 +163,39 @@ export function RateInput() {
<>
<styledEl.Wrapper>
<styledEl.Header>
<HeadingText inputCurrency={inputCurrency} currency={primaryCurrency} rateImpact={rateImpact} />

<styledEl.MarketPriceButton disabled={isDisabledMPrice} onClick={handleSetMarketPrice}>
<span>Set to market</span>
</styledEl.MarketPriceButton>
<HeadingText
inputCurrency={inputCurrency}
currency={primaryCurrency}
rateImpact={rateImpact}
toggleIcon={
<HoverTooltip
content="When enabled, the limit price stays fixed when changing the BUY amount. When disabled, the limit price will update based on the BUY amount changes."
wrapInContainer
placement="top-start"
>
<styledEl.LockIcon onClick={handleTogglePriceLock}>
<SVG src={limitPriceLocked ? LockedIcon : UnlockedIcon} width={12} height={10} />
</styledEl.LockIcon>
</HoverTooltip>
}
onToggle={handleToggle}
/>

{areBothCurrencies && (
<styledEl.MarketRateWrapper>
<i>Market:</i>{' '}
<styledEl.MarketPriceButton disabled={isDisabledMPrice} onClick={handleSetMarketPrice}>
{isLoadingMarketRate ? (
<styledEl.RateLoader size="14px" />
) : marketRate && !marketRate.equalTo(0) ? (
formatInputAmount(isInverted ? marketRate.invert() : marketRate)
) : (
''
)}
</styledEl.MarketPriceButton>
</styledEl.MarketRateWrapper>
)}
</styledEl.Header>

<styledEl.Body>
{isLoading && areBothCurrencies ? (
<styledEl.RateLoader />
Expand All @@ -163,42 +208,36 @@ export function RateInput() {
/>
)}

<styledEl.ActiveCurrency onClick={handleToggle}>
<styledEl.ActiveSymbol>
<TokenSymbol token={secondaryCurrency} />
</styledEl.ActiveSymbol>
<styledEl.ActiveIcon>
<RefreshCw size={12} />
</styledEl.ActiveIcon>
</styledEl.ActiveCurrency>
<styledEl.CurrencyToggleGroup>
<styledEl.ActiveCurrency onClick={handleToggle} $active={!isUsdMode}>
<styledEl.ActiveSymbol $active={!isUsdMode}>
<TokenLogo token={secondaryCurrency} size={16} />
<TokenSymbol token={secondaryCurrency} />
</styledEl.ActiveSymbol>
</styledEl.ActiveCurrency>

<styledEl.UsdButton onClick={handleToggleUsdMode} $active={isUsdMode}>
<SVG src={UsdIcon} />
</styledEl.UsdButton>
</styledEl.CurrencyToggleGroup>
</styledEl.Body>
</styledEl.Wrapper>

{ordersTableFeatures.DISPLAY_EST_EXECUTION_PRICE && (
<styledEl.EstimatedRate>
<b>
Order executes at{' '}
{isLoadingMarketRate ? (
<Loader size="14px" style={{ margin: '0 0 -2px 7px' }} />
) : executionPrice ? (
<HelpTooltip
text={
<ExecutionPriceTooltip
isInverted={isInverted}
feeAmount={feeAmount}
marketRate={marketRate}
displayedRate={displayedRate}
executionPrice={executionPrice}
/>
}
/>
) : null}
</b>
{!isLoadingMarketRate && executionPrice && (
<ExecutionPrice executionPrice={executionPrice} isInverted={isInverted} />
<styledEl.EstimatedRate>
<b>
{isLoadingMarketRate ? (
<styledEl.RateLoader size="14px" />
) : executionPrice ? (
<ExecutionPrice executionPrice={executionPrice} isInverted={isInverted} hideFiat />
) : (
'-'
)}
</styledEl.EstimatedRate>
)}
</b>
<span>
Estimated fill price
<HelpTooltip text="Network costs (incl. gas) are covered by filling your order when the market price is better than your limit price." />
</span>
</styledEl.EstimatedRate>
</>
)
}
Loading
Loading