improvement(swap): progress bar and more minimal default UI, also fix custom add/remove (#1069)
* add progress bar and minimal UI updates on swap * add hook to explicity check user added tokens, fixes add/remove bug * update with latest * remove confusing comment * update styles on loading, update arrow placement, code cleanup * fix typo on progress import
This commit is contained in:
parent
d6aa0e98a4
commit
975570fa97
@ -10,7 +10,7 @@ const Base = styled(RebassButton)<{
|
||||
padding?: string
|
||||
width?: string
|
||||
borderRadius?: string
|
||||
altDisbaledStyle?: boolean
|
||||
altDisabledStyle?: boolean
|
||||
}>`
|
||||
padding: ${({ padding }) => (padding ? padding : '18px')};
|
||||
width: ${({ width }) => (width ? width : '100%')};
|
||||
@ -53,12 +53,13 @@ export const ButtonPrimary = styled(Base)`
|
||||
background-color: ${({ theme }) => darken(0.1, theme.primary1)};
|
||||
}
|
||||
&:disabled {
|
||||
background-color: ${({ theme, altDisbaledStyle }) => (altDisbaledStyle ? theme.primary1 : theme.bg3)};
|
||||
color: ${({ theme, altDisbaledStyle }) => (altDisbaledStyle ? 'white' : theme.text3)};
|
||||
background-color: ${({ theme, altDisabledStyle }) => (altDisabledStyle ? theme.primary1 : theme.bg3)};
|
||||
color: ${({ theme, altDisabledStyle }) => (altDisabledStyle ? 'white' : theme.text3)};
|
||||
cursor: auto;
|
||||
box-shadow: none;
|
||||
border: 1px solid transparent;
|
||||
outline: none;
|
||||
opacity: ${({ altDisabledStyle }) => (altDisabledStyle ? '0.7' : '1')};
|
||||
}
|
||||
`
|
||||
|
||||
@ -253,11 +254,15 @@ const ButtonErrorStyle = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
export function ButtonConfirmed({ confirmed, ...rest }: { confirmed?: boolean } & ButtonProps) {
|
||||
export function ButtonConfirmed({
|
||||
confirmed,
|
||||
altDisabledStyle,
|
||||
...rest
|
||||
}: { confirmed?: boolean; altDisabledStyle?: boolean } & ButtonProps) {
|
||||
if (confirmed) {
|
||||
return <ButtonConfirmedStyle {...rest} />
|
||||
} else {
|
||||
return <ButtonPrimary {...rest} />
|
||||
return <ButtonPrimary {...rest} altDisabledStyle={altDisabledStyle} />
|
||||
}
|
||||
}
|
||||
|
||||
|
79
src/components/ProgressSteps/index.tsx
Normal file
79
src/components/ProgressSteps/index.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { RowBetween } from '../Row'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { transparentize } from 'polished'
|
||||
|
||||
const Wrapper = styled(AutoColumn)`
|
||||
margin-top: 1.25rem;
|
||||
`
|
||||
|
||||
const Grouping = styled(RowBetween)`
|
||||
width: 50%;
|
||||
`
|
||||
|
||||
const Circle = styled.div<{ confirmed?: boolean; disabled?: boolean }>`
|
||||
min-width: 20px;
|
||||
min-height: 20px;
|
||||
background-color: ${({ theme, confirmed, disabled }) =>
|
||||
disabled ? theme.bg4 : confirmed ? theme.green1 : theme.primary1};
|
||||
border-radius: 50%;
|
||||
color: ${({ theme }) => theme.white};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 8px;
|
||||
font-size: 12px;
|
||||
`
|
||||
|
||||
const CircleRow = styled.div`
|
||||
width: calc(100% - 20px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const Connector = styled.div<{ prevConfirmed?: boolean }>`
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: ;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
${({ theme, prevConfirmed }) => transparentize(0.5, prevConfirmed ? theme.green1 : theme.primary1)} 0%,
|
||||
${({ theme, prevConfirmed }) => (prevConfirmed ? theme.primary1 : theme.bg4)} 80%
|
||||
);
|
||||
opacity: 0.6;
|
||||
`
|
||||
|
||||
interface ProgressCirclesProps {
|
||||
steps: boolean[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on array of steps, create a step counter of circles.
|
||||
* A circle can be enabled, disabled, or confirmed. States are derived
|
||||
* from previous step.
|
||||
*
|
||||
* An extra circle is added to represent the ability to swap, add, or remove.
|
||||
* This step will never be marked as complete (because no 'txn done' state in body ui).
|
||||
*
|
||||
* @param steps array of booleans where true means step is complete
|
||||
*/
|
||||
export default function ProgressCircles({ steps }: ProgressCirclesProps) {
|
||||
return (
|
||||
<Wrapper justify={'center'}>
|
||||
<Grouping>
|
||||
{steps.map((step, i) => {
|
||||
return (
|
||||
<CircleRow key={i}>
|
||||
<Circle confirmed={step} disabled={!steps[i - 1] && i !== 0}>
|
||||
{step ? '✓' : i + 1}
|
||||
</Circle>
|
||||
<Connector prevConfirmed={step} />
|
||||
</CircleRow>
|
||||
)
|
||||
})}
|
||||
<Circle disabled={!steps[steps.length - 1]}>{steps.length + 1}</Circle>
|
||||
</Grouping>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
@ -8,6 +8,7 @@ import { useSelectedTokenList, WrappedTokenInfo } from '../../state/lists/hooks'
|
||||
import { useAddUserToken, useRemoveUserAddedToken } from '../../state/user/hooks'
|
||||
import { useCurrencyBalance } from '../../state/wallet/hooks'
|
||||
import { LinkStyledButton, TYPE } from '../../theme'
|
||||
import { useIsUserAddedToken } from '../../hooks/Tokens'
|
||||
import Column from '../Column'
|
||||
import { RowFixed } from '../Row'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
@ -96,12 +97,13 @@ function CurrencyRow({
|
||||
const key = currencyKey(currency)
|
||||
const selectedTokenList = useSelectedTokenList()
|
||||
const isOnSelectedList = isTokenOnList(selectedTokenList, currency)
|
||||
const customAdded = Boolean(!isOnSelectedList && currency instanceof Token)
|
||||
const customAdded = useIsUserAddedToken(currency)
|
||||
const balance = useCurrencyBalance(account ?? undefined, currency)
|
||||
|
||||
const removeToken = useRemoveUserAddedToken()
|
||||
const addToken = useAddUserToken()
|
||||
|
||||
// only show add or remove buttons if not on selected list
|
||||
return (
|
||||
<MenuItem
|
||||
style={style}
|
||||
@ -116,7 +118,7 @@ function CurrencyRow({
|
||||
{currency.symbol}
|
||||
</Text>
|
||||
<FadedSpan>
|
||||
{customAdded ? (
|
||||
{!isOnSelectedList && customAdded ? (
|
||||
<TYPE.main fontWeight={500}>
|
||||
Added by user
|
||||
<LinkStyledButton
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import { Currency, Price } from '@uniswap/sdk'
|
||||
import { Price } from '@uniswap/sdk'
|
||||
import { useContext } from 'react'
|
||||
import { Repeat } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
@ -8,27 +8,19 @@ import { StyledBalanceMaxMini } from './styleds'
|
||||
|
||||
interface TradePriceProps {
|
||||
price?: Price
|
||||
inputCurrency?: Currency
|
||||
outputCurrency?: Currency
|
||||
showInverted: boolean
|
||||
setShowInverted: (showInverted: boolean) => void
|
||||
}
|
||||
|
||||
export default function TradePrice({
|
||||
price,
|
||||
inputCurrency,
|
||||
outputCurrency,
|
||||
showInverted,
|
||||
setShowInverted
|
||||
}: TradePriceProps) {
|
||||
export default function TradePrice({ price, showInverted, setShowInverted }: TradePriceProps) {
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
const formattedPrice = showInverted ? price?.toSignificant(6) : price?.invert()?.toSignificant(6)
|
||||
|
||||
const show = Boolean(inputCurrency && outputCurrency)
|
||||
const show = Boolean(price?.baseCurrency && price?.quoteCurrency)
|
||||
const label = showInverted
|
||||
? `${outputCurrency?.symbol} per ${inputCurrency?.symbol}`
|
||||
: `${inputCurrency?.symbol} per ${outputCurrency?.symbol}`
|
||||
? `${price?.quoteCurrency?.symbol} per ${price?.baseCurrency?.symbol}`
|
||||
: `${price?.baseCurrency?.symbol} per ${price?.quoteCurrency?.symbol}`
|
||||
|
||||
return (
|
||||
<Text
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { parseBytes32String } from '@ethersproject/strings'
|
||||
import { Currency, ETHER, Token } from '@uniswap/sdk'
|
||||
import { Currency, ETHER, Token, currencyEquals } from '@uniswap/sdk'
|
||||
import { useMemo } from 'react'
|
||||
import { useSelectedTokenList } from '../state/lists/hooks'
|
||||
import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks'
|
||||
@ -32,6 +32,12 @@ export function useAllTokens(): { [address: string]: Token } {
|
||||
}, [chainId, userAddedTokens, allTokens])
|
||||
}
|
||||
|
||||
// Check if currency is included in custom list from user storage
|
||||
export function useIsUserAddedToken(currency: Currency): boolean {
|
||||
const userAddedTokens = useUserAddedTokens()
|
||||
return !!userAddedTokens.find(token => currencyEquals(currency, token))
|
||||
}
|
||||
|
||||
// parse a name or symbol from a token response
|
||||
const BYTES32_REGEX = /^0x[a-fA-F0-9]{64}$/
|
||||
function parseStringOrBytes32(str: string | undefined, bytes32: string | undefined, defaultValue: string): string {
|
||||
|
@ -5,7 +5,7 @@ import ReactGA from 'react-ga'
|
||||
import { Text } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import AddressInputPanel from '../../components/AddressInputPanel'
|
||||
import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
|
||||
import { ButtonError, ButtonLight, ButtonPrimary, ButtonConfirmed } from '../../components/Button'
|
||||
import Card, { GreyCard } from '../../components/Card'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import ConfirmSwapModal from '../../components/swap/ConfirmSwapModal'
|
||||
@ -15,9 +15,10 @@ import { AutoRow, RowBetween } from '../../components/Row'
|
||||
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
|
||||
import BetterTradeLink from '../../components/swap/BetterTradeLink'
|
||||
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
|
||||
import { ArrowWrapper, BottomGrouping, Dots, SwapCallbackError, Wrapper } from '../../components/swap/styleds'
|
||||
import { ArrowWrapper, BottomGrouping, SwapCallbackError, Wrapper } from '../../components/swap/styleds'
|
||||
import TradePrice from '../../components/swap/TradePrice'
|
||||
import TokenWarningModal from '../../components/TokenWarningModal'
|
||||
import ProgressSteps from '../../components/ProgressSteps'
|
||||
|
||||
import { BETTER_TRADE_LINK_THRESHOLD, INITIAL_ALLOWED_SLIPPAGE } from '../../constants'
|
||||
import { getTradeVersion, isTradeBetter } from '../../data/V1'
|
||||
@ -42,6 +43,7 @@ import { maxAmountSpend } from '../../utils/maxAmountSpend'
|
||||
import { computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
|
||||
import AppBody from '../AppBody'
|
||||
import { ClickableText } from '../Pool/styleds'
|
||||
import Loader from '../../components/Loader'
|
||||
|
||||
export default function Swap() {
|
||||
const loadedUrlParams = useDefaultsFromURLSearch()
|
||||
@ -294,7 +296,7 @@ export default function Swap() {
|
||||
|
||||
<AutoColumn gap={'md'}>
|
||||
<CurrencyInputPanel
|
||||
label={independentField === Field.OUTPUT && !showWrap ? 'From (estimated)' : 'From'}
|
||||
label={independentField === Field.OUTPUT && !showWrap && trade ? 'From (estimated)' : 'From'}
|
||||
value={formattedAmounts[Field.INPUT]}
|
||||
showMaxButton={!atMaxAmountInput}
|
||||
currency={currencies[Field.INPUT]}
|
||||
@ -304,9 +306,8 @@ export default function Swap() {
|
||||
otherCurrency={currencies[Field.OUTPUT]}
|
||||
id="swap-currency-input"
|
||||
/>
|
||||
|
||||
<AutoColumn justify="space-between">
|
||||
<AutoRow justify="space-between" style={{ padding: '0 1rem' }}>
|
||||
<AutoRow justify={isExpertMode ? 'space-between' : 'center'} style={{ padding: '0 1rem' }}>
|
||||
<ArrowWrapper clickable>
|
||||
<ArrowDown
|
||||
size="16"
|
||||
@ -327,7 +328,7 @@ export default function Swap() {
|
||||
<CurrencyInputPanel
|
||||
value={formattedAmounts[Field.OUTPUT]}
|
||||
onUserInput={handleTypeOutput}
|
||||
label={independentField === Field.INPUT && !showWrap ? 'To (estimated)' : 'To'}
|
||||
label={independentField === Field.INPUT && !showWrap && trade ? 'To (estimated)' : 'To'}
|
||||
showMaxButton={false}
|
||||
currency={currencies[Field.OUTPUT]}
|
||||
onCurrencySelect={handleOutputSelect}
|
||||
@ -352,19 +353,18 @@ export default function Swap() {
|
||||
{showWrap ? null : (
|
||||
<Card padding={'.25rem .75rem 0 .75rem'} borderRadius={'20px'}>
|
||||
<AutoColumn gap="4px">
|
||||
{Boolean(trade) && (
|
||||
<RowBetween align="center">
|
||||
<Text fontWeight={500} fontSize={14} color={theme.text2}>
|
||||
Price
|
||||
</Text>
|
||||
<TradePrice
|
||||
inputCurrency={currencies[Field.INPUT]}
|
||||
outputCurrency={currencies[Field.OUTPUT]}
|
||||
price={trade?.executionPrice}
|
||||
showInverted={showInverted}
|
||||
setShowInverted={setShowInverted}
|
||||
/>
|
||||
</RowBetween>
|
||||
|
||||
)}
|
||||
{allowedSlippage !== INITIAL_ALLOWED_SLIPPAGE && (
|
||||
<RowBetween align="center">
|
||||
<ClickableText fontWeight={500} fontSize={14} color={theme.text2} onClick={toggleSettings}>
|
||||
@ -393,20 +393,23 @@ export default function Swap() {
|
||||
</GreyCard>
|
||||
) : showApproveFlow ? (
|
||||
<RowBetween>
|
||||
<ButtonPrimary
|
||||
<ButtonConfirmed
|
||||
onClick={approveCallback}
|
||||
disabled={approval !== ApprovalState.NOT_APPROVED || approvalSubmitted}
|
||||
width="48%"
|
||||
altDisbaledStyle={approval === ApprovalState.PENDING} // show solid button while waiting
|
||||
altDisabledStyle={approval === ApprovalState.PENDING} // show solid button while waiting
|
||||
confirmed={approval === ApprovalState.APPROVED}
|
||||
>
|
||||
{approval === ApprovalState.PENDING ? (
|
||||
<Dots>Approving</Dots>
|
||||
<AutoRow gap="6px" justify="center">
|
||||
Approving <Loader stroke="white" />
|
||||
</AutoRow>
|
||||
) : approvalSubmitted && approval === ApprovalState.APPROVED ? (
|
||||
'Approved'
|
||||
) : (
|
||||
'Approve ' + currencies[Field.INPUT]?.symbol
|
||||
)}
|
||||
</ButtonPrimary>
|
||||
</ButtonConfirmed>
|
||||
<ButtonError
|
||||
onClick={() => {
|
||||
if (isExpertMode) {
|
||||
@ -463,6 +466,7 @@ export default function Swap() {
|
||||
</Text>
|
||||
</ButtonError>
|
||||
)}
|
||||
{showApproveFlow && <ProgressSteps steps={[approval === ApprovalState.APPROVED]} />}
|
||||
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
|
||||
{betterTradeLinkVersion && <BetterTradeLink version={betterTradeLinkVersion} />}
|
||||
</BottomGrouping>
|
||||
|
Loading…
Reference in New Issue
Block a user